aicodeman 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -0
- package/dist/ai-idle-checker.d.ts.map +1 -1
- package/dist/ai-idle-checker.js +3 -2
- package/dist/ai-idle-checker.js.map +1 -1
- package/dist/ai-plan-checker.d.ts.map +1 -1
- package/dist/ai-plan-checker.js +3 -2
- package/dist/ai-plan-checker.js.map +1 -1
- package/dist/bash-tool-parser.d.ts +2 -3
- package/dist/bash-tool-parser.d.ts.map +1 -1
- package/dist/bash-tool-parser.js +14 -31
- package/dist/bash-tool-parser.js.map +1 -1
- package/dist/config/ai-defaults.d.ts +16 -0
- package/dist/config/ai-defaults.d.ts.map +1 -0
- package/dist/config/ai-defaults.js +16 -0
- package/dist/config/ai-defaults.js.map +1 -0
- package/dist/config/auth-config.d.ts +19 -0
- package/dist/config/auth-config.d.ts.map +1 -0
- package/dist/config/auth-config.js +28 -0
- package/dist/config/auth-config.js.map +1 -0
- package/dist/config/exec-timeout.d.ts +10 -0
- package/dist/config/exec-timeout.d.ts.map +1 -0
- package/dist/config/exec-timeout.js +10 -0
- package/dist/config/exec-timeout.js.map +1 -0
- package/dist/config/map-limits.d.ts +4 -0
- package/dist/config/map-limits.d.ts.map +1 -1
- package/dist/config/map-limits.js +7 -0
- package/dist/config/map-limits.js.map +1 -1
- package/dist/config/server-timing.d.ts +36 -0
- package/dist/config/server-timing.d.ts.map +1 -0
- package/dist/config/server-timing.js +51 -0
- package/dist/config/server-timing.js.map +1 -0
- package/dist/config/team-config.d.ts +16 -0
- package/dist/config/team-config.d.ts.map +1 -0
- package/dist/config/team-config.js +16 -0
- package/dist/config/team-config.js.map +1 -0
- package/dist/config/terminal-limits.d.ts +18 -0
- package/dist/config/terminal-limits.d.ts.map +1 -0
- package/dist/config/terminal-limits.js +18 -0
- package/dist/config/terminal-limits.js.map +1 -0
- package/dist/config/tunnel-config.d.ts +27 -0
- package/dist/config/tunnel-config.d.ts.map +1 -0
- package/dist/config/tunnel-config.js +36 -0
- package/dist/config/tunnel-config.js.map +1 -0
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +7 -6
- package/dist/hooks-config.js.map +1 -1
- package/dist/image-watcher.d.ts +4 -4
- package/dist/image-watcher.d.ts.map +1 -1
- package/dist/image-watcher.js +17 -30
- package/dist/image-watcher.js.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/plan-orchestrator.d.ts +2 -24
- package/dist/plan-orchestrator.d.ts.map +1 -1
- package/dist/plan-orchestrator.js.map +1 -1
- package/dist/push-store.d.ts +1 -1
- package/dist/push-store.d.ts.map +1 -1
- package/dist/push-store.js +4 -12
- package/dist/push-store.js.map +1 -1
- package/dist/ralph-fix-plan-watcher.d.ts +91 -0
- package/dist/ralph-fix-plan-watcher.d.ts.map +1 -0
- package/dist/ralph-fix-plan-watcher.js +326 -0
- package/dist/ralph-fix-plan-watcher.js.map +1 -0
- package/dist/ralph-plan-tracker.d.ts +201 -0
- package/dist/ralph-plan-tracker.d.ts.map +1 -0
- package/dist/ralph-plan-tracker.js +325 -0
- package/dist/ralph-plan-tracker.js.map +1 -0
- package/dist/ralph-stall-detector.d.ts +84 -0
- package/dist/ralph-stall-detector.d.ts.map +1 -0
- package/dist/ralph-stall-detector.js +139 -0
- package/dist/ralph-stall-detector.js.map +1 -0
- package/dist/ralph-status-parser.d.ts +141 -0
- package/dist/ralph-status-parser.d.ts.map +1 -0
- package/dist/ralph-status-parser.js +478 -0
- package/dist/ralph-status-parser.js.map +1 -0
- package/dist/ralph-tracker.d.ts +194 -685
- package/dist/ralph-tracker.d.ts.map +1 -1
- package/dist/ralph-tracker.js +349 -1713
- package/dist/ralph-tracker.js.map +1 -1
- package/dist/respawn-adaptive-timing.d.ts +61 -0
- package/dist/respawn-adaptive-timing.d.ts.map +1 -0
- package/dist/respawn-adaptive-timing.js +105 -0
- package/dist/respawn-adaptive-timing.js.map +1 -0
- package/dist/respawn-controller.d.ts +14 -101
- package/dist/respawn-controller.d.ts.map +1 -1
- package/dist/respawn-controller.js +155 -594
- package/dist/respawn-controller.js.map +1 -1
- package/dist/respawn-health.d.ts +54 -0
- package/dist/respawn-health.d.ts.map +1 -0
- package/dist/respawn-health.js +183 -0
- package/dist/respawn-health.js.map +1 -0
- package/dist/respawn-metrics.d.ts +81 -0
- package/dist/respawn-metrics.d.ts.map +1 -0
- package/dist/respawn-metrics.js +198 -0
- package/dist/respawn-metrics.js.map +1 -0
- package/dist/respawn-patterns.d.ts +45 -0
- package/dist/respawn-patterns.d.ts.map +1 -0
- package/dist/respawn-patterns.js +125 -0
- package/dist/respawn-patterns.js.map +1 -0
- package/dist/session-auto-ops.d.ts +89 -0
- package/dist/session-auto-ops.d.ts.map +1 -0
- package/dist/session-auto-ops.js +224 -0
- package/dist/session-auto-ops.js.map +1 -0
- package/dist/session-cli-builder.d.ts +62 -0
- package/dist/session-cli-builder.d.ts.map +1 -0
- package/dist/session-cli-builder.js +121 -0
- package/dist/session-cli-builder.js.map +1 -0
- package/dist/session-task-cache.d.ts +52 -0
- package/dist/session-task-cache.d.ts.map +1 -0
- package/dist/session-task-cache.js +90 -0
- package/dist/session-task-cache.js.map +1 -0
- package/dist/session.d.ts +2 -33
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +58 -309
- package/dist/session.js.map +1 -1
- package/dist/state-store.d.ts +9 -2
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +112 -39
- package/dist/state-store.js.map +1 -1
- package/dist/subagent-watcher.d.ts +16 -9
- package/dist/subagent-watcher.d.ts.map +1 -1
- package/dist/subagent-watcher.js +126 -147
- package/dist/subagent-watcher.js.map +1 -1
- package/dist/team-watcher.d.ts +3 -0
- package/dist/team-watcher.d.ts.map +1 -1
- package/dist/team-watcher.js +54 -5
- package/dist/team-watcher.js.map +1 -1
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +1 -2
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tunnel-manager.d.ts +26 -0
- package/dist/tunnel-manager.d.ts.map +1 -1
- package/dist/tunnel-manager.js +127 -7
- package/dist/tunnel-manager.js.map +1 -1
- package/dist/types/api.d.ts +93 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +83 -0
- package/dist/types/api.js.map +1 -0
- package/dist/types/app-state.d.ts +100 -0
- package/dist/types/app-state.d.ts.map +1 -0
- package/dist/types/app-state.js +59 -0
- package/dist/types/app-state.js.map +1 -0
- package/dist/types/common.d.ts +70 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +8 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +18 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/lifecycle.d.ts +17 -0
- package/dist/types/lifecycle.d.ts.map +1 -0
- package/dist/types/lifecycle.js +5 -0
- package/dist/types/lifecycle.js.map +1 -0
- package/dist/types/plan.d.ts +32 -0
- package/dist/types/plan.d.ts.map +1 -0
- package/dist/types/plan.js +5 -0
- package/dist/types/plan.js.map +1 -0
- package/dist/types/push.d.ts +23 -0
- package/dist/types/push.d.ts.map +1 -0
- package/dist/types/push.js +5 -0
- package/dist/types/push.js.map +1 -0
- package/dist/types/ralph.d.ts +241 -0
- package/dist/types/ralph.d.ts.map +1 -0
- package/dist/types/ralph.js +49 -0
- package/dist/types/ralph.js.map +1 -0
- package/dist/types/respawn.d.ts +250 -0
- package/dist/types/respawn.d.ts.map +1 -0
- package/dist/types/respawn.js +5 -0
- package/dist/types/respawn.js.map +1 -0
- package/dist/types/run-summary.d.ts +81 -0
- package/dist/types/run-summary.d.ts.map +1 -0
- package/dist/types/run-summary.js +22 -0
- package/dist/types/run-summary.js.map +1 -0
- package/dist/types/session.d.ts +130 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +5 -0
- package/dist/types/session.js.map +1 -0
- package/dist/types/task.d.ts +58 -0
- package/dist/types/task.d.ts.map +1 -0
- package/dist/types/task.js +5 -0
- package/dist/types/task.js.map +1 -0
- package/dist/types/teams.d.ts +55 -0
- package/dist/types/teams.d.ts.map +1 -0
- package/dist/types/teams.js +5 -0
- package/dist/types/teams.js.map +1 -0
- package/dist/types/tools.d.ts +46 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +5 -0
- package/dist/types/tools.js.map +1 -0
- package/dist/types.d.ts +1 -1138
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -214
- package/dist/types.js.map +1 -1
- package/dist/utils/claude-cli-resolver.d.ts.map +1 -1
- package/dist/utils/claude-cli-resolver.js +1 -2
- package/dist/utils/claude-cli-resolver.js.map +1 -1
- package/dist/utils/debouncer.d.ts +111 -0
- package/dist/utils/debouncer.d.ts.map +1 -0
- package/dist/utils/debouncer.js +162 -0
- package/dist/utils/debouncer.js.map +1 -0
- package/dist/utils/index.d.ts +3 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/opencode-cli-resolver.d.ts.map +1 -1
- package/dist/utils/opencode-cli-resolver.js +1 -2
- package/dist/utils/opencode-cli-resolver.js.map +1 -1
- package/dist/utils/string-similarity.d.ts +0 -57
- package/dist/utils/string-similarity.d.ts.map +1 -1
- package/dist/utils/string-similarity.js +3 -18
- package/dist/utils/string-similarity.js.map +1 -1
- package/dist/web/middleware/auth.d.ts +31 -0
- package/dist/web/middleware/auth.d.ts.map +1 -0
- package/dist/web/middleware/auth.js +154 -0
- package/dist/web/middleware/auth.js.map +1 -0
- package/dist/web/ports/auth-port.d.ts +18 -0
- package/dist/web/ports/auth-port.d.ts.map +1 -0
- package/dist/web/ports/auth-port.js +6 -0
- package/dist/web/ports/auth-port.js.map +1 -0
- package/dist/web/ports/config-port.d.ts +28 -0
- package/dist/web/ports/config-port.d.ts.map +1 -0
- package/dist/web/ports/config-port.js +6 -0
- package/dist/web/ports/config-port.js.map +1 -0
- package/dist/web/ports/event-port.d.ts +13 -0
- package/dist/web/ports/event-port.d.ts.map +1 -0
- package/dist/web/ports/event-port.js +6 -0
- package/dist/web/ports/event-port.js.map +1 -0
- package/dist/web/ports/index.d.ts +14 -0
- package/dist/web/ports/index.d.ts.map +1 -0
- package/dist/web/ports/index.js +9 -0
- package/dist/web/ports/index.js.map +1 -0
- package/dist/web/ports/infra-port.d.ts +36 -0
- package/dist/web/ports/infra-port.d.ts.map +1 -0
- package/dist/web/ports/infra-port.js +6 -0
- package/dist/web/ports/infra-port.js.map +1 -0
- package/dist/web/ports/respawn-port.d.ts +20 -0
- package/dist/web/ports/respawn-port.d.ts.map +1 -0
- package/dist/web/ports/respawn-port.js +6 -0
- package/dist/web/ports/respawn-port.js.map +1 -0
- package/dist/web/ports/session-port.d.ts +15 -0
- package/dist/web/ports/session-port.d.ts.map +1 -0
- package/dist/web/ports/session-port.js +6 -0
- package/dist/web/ports/session-port.js.map +1 -0
- package/dist/web/public/api-client.js +70 -0
- package/dist/web/public/api-client.js.br +0 -0
- package/dist/web/public/api-client.js.gz +0 -0
- package/dist/web/public/app.js +152 -236
- package/dist/web/public/app.js.br +0 -0
- package/dist/web/public/app.js.gz +0 -0
- package/dist/web/public/constants.js +238 -0
- package/dist/web/public/constants.js.br +0 -0
- package/dist/web/public/constants.js.gz +0 -0
- package/dist/web/public/index.html +11 -3
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/keyboard-accessory.js +279 -0
- package/dist/web/public/keyboard-accessory.js.br +0 -0
- package/dist/web/public/keyboard-accessory.js.gz +0 -0
- package/dist/web/public/mobile-handlers.js +467 -0
- package/dist/web/public/mobile-handlers.js.br +0 -0
- package/dist/web/public/mobile-handlers.js.gz +0 -0
- package/dist/web/public/mobile.css.gz +0 -0
- package/dist/web/public/notification-manager.js +445 -0
- package/dist/web/public/notification-manager.js.br +0 -0
- package/dist/web/public/notification-manager.js.gz +0 -0
- package/dist/web/public/ralph-wizard.js +3 -3
- package/dist/web/public/ralph-wizard.js.br +0 -0
- package/dist/web/public/ralph-wizard.js.gz +0 -0
- package/dist/web/public/styles.css.gz +0 -0
- package/dist/web/public/subagent-windows.js +1115 -0
- package/dist/web/public/subagent-windows.js.br +0 -0
- package/dist/web/public/subagent-windows.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.js +858 -0
- package/dist/web/public/voice-input.js.br +0 -0
- package/dist/web/public/voice-input.js.gz +0 -0
- package/dist/web/route-helpers.d.ts +38 -0
- package/dist/web/route-helpers.d.ts.map +1 -0
- package/dist/web/route-helpers.js +143 -0
- package/dist/web/route-helpers.js.map +1 -0
- package/dist/web/routes/case-routes.d.ts +9 -0
- package/dist/web/routes/case-routes.d.ts.map +1 -0
- package/dist/web/routes/case-routes.js +419 -0
- package/dist/web/routes/case-routes.js.map +1 -0
- package/dist/web/routes/file-routes.d.ts +8 -0
- package/dist/web/routes/file-routes.d.ts.map +1 -0
- package/dist/web/routes/file-routes.js +337 -0
- package/dist/web/routes/file-routes.js.map +1 -0
- package/dist/web/routes/hook-event-routes.d.ts +9 -0
- package/dist/web/routes/hook-event-routes.d.ts.map +1 -0
- package/dist/web/routes/hook-event-routes.js +57 -0
- package/dist/web/routes/hook-event-routes.js.map +1 -0
- package/dist/web/routes/index.d.ts +16 -0
- package/dist/web/routes/index.d.ts.map +1 -0
- package/dist/web/routes/index.js +16 -0
- package/dist/web/routes/index.js.map +1 -0
- package/dist/web/routes/mux-routes.d.ts +8 -0
- package/dist/web/routes/mux-routes.d.ts.map +1 -0
- package/dist/web/routes/mux-routes.js +32 -0
- package/dist/web/routes/mux-routes.js.map +1 -0
- package/dist/web/routes/plan-routes.d.ts +9 -0
- package/dist/web/routes/plan-routes.d.ts.map +1 -0
- package/dist/web/routes/plan-routes.js +381 -0
- package/dist/web/routes/plan-routes.js.map +1 -0
- package/dist/web/routes/push-routes.d.ts +8 -0
- package/dist/web/routes/push-routes.d.ts.map +1 -0
- package/dist/web/routes/push-routes.js +49 -0
- package/dist/web/routes/push-routes.js.map +1 -0
- package/dist/web/routes/ralph-routes.d.ts +9 -0
- package/dist/web/routes/ralph-routes.d.ts.map +1 -0
- package/dist/web/routes/ralph-routes.js +475 -0
- package/dist/web/routes/ralph-routes.js.map +1 -0
- package/dist/web/routes/respawn-routes.d.ts +8 -0
- package/dist/web/routes/respawn-routes.d.ts.map +1 -0
- package/dist/web/routes/respawn-routes.js +260 -0
- package/dist/web/routes/respawn-routes.js.map +1 -0
- package/dist/web/routes/scheduled-routes.d.ts +8 -0
- package/dist/web/routes/scheduled-routes.d.ts.map +1 -0
- package/dist/web/routes/scheduled-routes.js +51 -0
- package/dist/web/routes/scheduled-routes.js.map +1 -0
- package/dist/web/routes/session-routes.d.ts +9 -0
- package/dist/web/routes/session-routes.d.ts.map +1 -0
- package/dist/web/routes/session-routes.js +729 -0
- package/dist/web/routes/session-routes.js.map +1 -0
- package/dist/web/routes/system-routes.d.ts +9 -0
- package/dist/web/routes/system-routes.d.ts.map +1 -0
- package/dist/web/routes/system-routes.js +678 -0
- package/dist/web/routes/system-routes.js.map +1 -0
- package/dist/web/routes/team-routes.d.ts +8 -0
- package/dist/web/routes/team-routes.d.ts.map +1 -0
- package/dist/web/routes/team-routes.js +14 -0
- package/dist/web/routes/team-routes.js.map +1 -0
- package/dist/web/schemas.d.ts +43 -3
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +6 -2
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +10 -9
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +342 -3829
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// Codeman — Shared constants and utility functions for frontend modules
|
|
2
|
+
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Web Push Utilities
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
/** Convert a base64-encoded VAPID key to Uint8Array for pushManager.subscribe() */
|
|
8
|
+
function urlBase64ToUint8Array(base64String) {
|
|
9
|
+
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
|
10
|
+
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
|
|
11
|
+
const rawData = atob(base64);
|
|
12
|
+
const outputArray = new Uint8Array(rawData.length);
|
|
13
|
+
for (let i = 0; i < rawData.length; ++i) {
|
|
14
|
+
outputArray[i] = rawData.charCodeAt(i);
|
|
15
|
+
}
|
|
16
|
+
return outputArray;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Constants
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
// Default terminal scrollback (can be changed via settings)
|
|
24
|
+
const DEFAULT_SCROLLBACK = 5000;
|
|
25
|
+
|
|
26
|
+
// Timing constants
|
|
27
|
+
const STUCK_THRESHOLD_DEFAULT_MS = 600000; // 10 minutes - default for stuck detection
|
|
28
|
+
const GROUPING_TIMEOUT_MS = 5000; // 5 seconds - notification grouping window
|
|
29
|
+
const NOTIFICATION_LIST_CAP = 100; // Max notifications in list
|
|
30
|
+
const TITLE_FLASH_INTERVAL_MS = 1500; // Title flash rate
|
|
31
|
+
const BROWSER_NOTIF_RATE_LIMIT_MS = 3000; // Rate limit for browser notifications
|
|
32
|
+
const AUTO_CLOSE_NOTIFICATION_MS = 8000; // Auto-close browser notifications
|
|
33
|
+
const THROTTLE_DELAY_MS = 100; // General UI throttle delay
|
|
34
|
+
const TERMINAL_CHUNK_SIZE = 128 * 1024; // 128KB chunks for terminal data
|
|
35
|
+
const TERMINAL_TAIL_SIZE = 256 * 1024; // 256KB tail for initial load
|
|
36
|
+
const SYNC_WAIT_TIMEOUT_MS = 50; // Wait timeout for terminal sync
|
|
37
|
+
const STATS_POLLING_INTERVAL_MS = 2000; // System stats polling
|
|
38
|
+
|
|
39
|
+
// Z-index base values for layered floating windows
|
|
40
|
+
const ZINDEX_SUBAGENT_BASE = 1000;
|
|
41
|
+
const ZINDEX_PLAN_SUBAGENT_BASE = 1100;
|
|
42
|
+
const ZINDEX_LOG_VIEWER_BASE = 2000;
|
|
43
|
+
const ZINDEX_IMAGE_POPUP_BASE = 3000;
|
|
44
|
+
|
|
45
|
+
// Subagent/floating window layout
|
|
46
|
+
const WINDOW_INITIAL_TOP_PX = 120;
|
|
47
|
+
const WINDOW_CASCADE_OFFSET_PX = 30;
|
|
48
|
+
const WINDOW_MIN_WIDTH_PX = 200;
|
|
49
|
+
const WINDOW_MIN_HEIGHT_PX = 200;
|
|
50
|
+
const WINDOW_DEFAULT_WIDTH_PX = 300;
|
|
51
|
+
|
|
52
|
+
// Scheduler API — prioritize terminal writes over background UI updates.
|
|
53
|
+
// scheduler.postTask('background') defers non-critical work (connection lines, panel renders)
|
|
54
|
+
// so the main thread stays free for terminal rendering at 60fps.
|
|
55
|
+
const _hasScheduler = typeof globalThis.scheduler?.postTask === 'function';
|
|
56
|
+
function scheduleBackground(fn) {
|
|
57
|
+
if (_hasScheduler) { scheduler.postTask(fn, { priority: 'background' }); }
|
|
58
|
+
else { requestAnimationFrame(fn); }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// DEC mode 2026 - Synchronized Output
|
|
62
|
+
// Wrap terminal writes with these markers to prevent partial-frame flicker.
|
|
63
|
+
// Terminal buffers all output between markers and renders atomically.
|
|
64
|
+
// Supported by: WezTerm, Kitty, Ghostty, iTerm2 3.5+, Windows Terminal, VSCode terminal
|
|
65
|
+
// xterm.js doesn't support DEC 2026 natively, so we implement buffering ourselves.
|
|
66
|
+
const DEC_SYNC_START = '\x1b[?2026h';
|
|
67
|
+
const DEC_SYNC_END = '\x1b[?2026l';
|
|
68
|
+
// Pre-compiled regex for stripping DEC 2026 markers (single pass instead of two replaceAll calls)
|
|
69
|
+
const DEC_SYNC_STRIP_RE = /\x1b\[\?2026[hl]/g;
|
|
70
|
+
|
|
71
|
+
// Built-in respawn configuration presets
|
|
72
|
+
const BUILTIN_RESPAWN_PRESETS = [
|
|
73
|
+
{
|
|
74
|
+
id: 'solo-work',
|
|
75
|
+
name: 'Solo',
|
|
76
|
+
description: 'Claude working alone — fast respawn cycles with context reset',
|
|
77
|
+
config: {
|
|
78
|
+
idleTimeoutMs: 3000,
|
|
79
|
+
updatePrompt: 'summarize your progress so far before the context reset.',
|
|
80
|
+
interStepDelayMs: 2000,
|
|
81
|
+
sendClear: true,
|
|
82
|
+
sendInit: true,
|
|
83
|
+
kickstartPrompt: 'continue working. Pick up where you left off based on the context above.',
|
|
84
|
+
autoAcceptPrompts: true,
|
|
85
|
+
},
|
|
86
|
+
durationMinutes: 60,
|
|
87
|
+
builtIn: true,
|
|
88
|
+
createdAt: 0,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: 'subagent-workflow',
|
|
92
|
+
name: 'Subagents',
|
|
93
|
+
description: 'Lead session with Task tool subagents — longer idle tolerance',
|
|
94
|
+
config: {
|
|
95
|
+
idleTimeoutMs: 45000,
|
|
96
|
+
updatePrompt: 'check on your running subagents and summarize their results before the context reset. If all subagents have finished, note what was completed and what remains.',
|
|
97
|
+
interStepDelayMs: 3000,
|
|
98
|
+
sendClear: true,
|
|
99
|
+
sendInit: true,
|
|
100
|
+
kickstartPrompt: 'check on your running subagents and continue coordinating their work. If all subagents have finished, summarize their results and proceed with the next step.',
|
|
101
|
+
autoAcceptPrompts: true,
|
|
102
|
+
},
|
|
103
|
+
durationMinutes: 240,
|
|
104
|
+
builtIn: true,
|
|
105
|
+
createdAt: 0,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'team-lead',
|
|
109
|
+
name: 'Team',
|
|
110
|
+
description: 'Leading an agent team via TeamCreate — tolerates long silences',
|
|
111
|
+
config: {
|
|
112
|
+
idleTimeoutMs: 90000,
|
|
113
|
+
updatePrompt: 'review the task list and teammate progress. Summarize the current state before the context reset.',
|
|
114
|
+
interStepDelayMs: 5000,
|
|
115
|
+
sendClear: true,
|
|
116
|
+
sendInit: true,
|
|
117
|
+
kickstartPrompt: 'check on your teammates by reviewing the task list and any messages in your inbox. Assign new tasks if teammates are idle, or continue coordinating the team effort.',
|
|
118
|
+
autoAcceptPrompts: true,
|
|
119
|
+
},
|
|
120
|
+
durationMinutes: 480,
|
|
121
|
+
builtIn: true,
|
|
122
|
+
createdAt: 0,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: 'ralph-todo',
|
|
126
|
+
name: 'Ralph/Todo',
|
|
127
|
+
description: 'Ralph Loop task list — works through todos with progress tracking',
|
|
128
|
+
config: {
|
|
129
|
+
idleTimeoutMs: 8000,
|
|
130
|
+
updatePrompt: 'update CLAUDE.md with discoveries and progress notes, mark completed tasks in @fix_plan.md, write a brief summary so the next cycle can continue seamlessly.',
|
|
131
|
+
interStepDelayMs: 3000,
|
|
132
|
+
sendClear: true,
|
|
133
|
+
sendInit: true,
|
|
134
|
+
kickstartPrompt: 'read @fix_plan.md for task status, continue on the next uncompleted task. When ALL tasks are complete, output <promise>COMPLETE</promise>.',
|
|
135
|
+
autoAcceptPrompts: true,
|
|
136
|
+
},
|
|
137
|
+
durationMinutes: 480,
|
|
138
|
+
builtIn: true,
|
|
139
|
+
createdAt: 0,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: 'overnight-autonomous',
|
|
143
|
+
name: 'Overnight',
|
|
144
|
+
description: 'Unattended overnight runs with full context reset between cycles',
|
|
145
|
+
config: {
|
|
146
|
+
idleTimeoutMs: 10000,
|
|
147
|
+
updatePrompt: 'summarize what you accomplished so far and write key progress notes to CLAUDE.md so the next cycle can pick up where you left off.',
|
|
148
|
+
interStepDelayMs: 3000,
|
|
149
|
+
sendClear: true,
|
|
150
|
+
sendInit: true,
|
|
151
|
+
kickstartPrompt: 'continue working on the task. Pick up where you left off based on the context above.',
|
|
152
|
+
autoAcceptPrompts: true,
|
|
153
|
+
},
|
|
154
|
+
durationMinutes: 480,
|
|
155
|
+
builtIn: true,
|
|
156
|
+
createdAt: 0,
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// Utility Functions
|
|
162
|
+
// ============================================================================
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get unified coordinates from mouse or touch event.
|
|
166
|
+
* @param {MouseEvent|TouchEvent} e - The event
|
|
167
|
+
* @returns {{ clientX: number, clientY: number }} Coordinates
|
|
168
|
+
*/
|
|
169
|
+
function getEventCoords(e) {
|
|
170
|
+
if (e.touches && e.touches.length > 0) {
|
|
171
|
+
return { clientX: e.touches[0].clientX, clientY: e.touches[0].clientY };
|
|
172
|
+
}
|
|
173
|
+
if (e.changedTouches && e.changedTouches.length > 0) {
|
|
174
|
+
return { clientX: e.changedTouches[0].clientX, clientY: e.changedTouches[0].clientY };
|
|
175
|
+
}
|
|
176
|
+
return { clientX: e.clientX, clientY: e.clientY };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Process data containing DEC 2026 sync markers.
|
|
181
|
+
* Strips markers and returns segments that should be written atomically.
|
|
182
|
+
* Each returned segment represents content between SYNC_START and SYNC_END.
|
|
183
|
+
* Content outside sync blocks is returned as-is.
|
|
184
|
+
*
|
|
185
|
+
* @param {string} data - Raw terminal data with potential sync markers
|
|
186
|
+
* @returns {string[]} - Array of content segments to write (markers stripped)
|
|
187
|
+
*/
|
|
188
|
+
function extractSyncSegments(data) {
|
|
189
|
+
const segments = [];
|
|
190
|
+
let remaining = data;
|
|
191
|
+
|
|
192
|
+
while (remaining.length > 0) {
|
|
193
|
+
const startIdx = remaining.indexOf(DEC_SYNC_START);
|
|
194
|
+
|
|
195
|
+
if (startIdx === -1) {
|
|
196
|
+
// No more sync blocks, return rest as-is
|
|
197
|
+
if (remaining.length > 0) {
|
|
198
|
+
segments.push(remaining);
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Content before sync block (if any)
|
|
204
|
+
if (startIdx > 0) {
|
|
205
|
+
segments.push(remaining.slice(0, startIdx));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Find matching end marker
|
|
209
|
+
const afterStart = remaining.slice(startIdx + DEC_SYNC_START.length);
|
|
210
|
+
const endIdx = afterStart.indexOf(DEC_SYNC_END);
|
|
211
|
+
|
|
212
|
+
if (endIdx === -1) {
|
|
213
|
+
// No end marker found - sync block continues in next chunk
|
|
214
|
+
// Include the start marker so it can be handled when more data arrives
|
|
215
|
+
segments.push(remaining.slice(startIdx));
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Extract synchronized content (without markers)
|
|
220
|
+
const syncContent = afterStart.slice(0, endIdx);
|
|
221
|
+
if (syncContent.length > 0) {
|
|
222
|
+
segments.push(syncContent);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Continue with content after end marker
|
|
226
|
+
remaining = afterStart.slice(endIdx + DEC_SYNC_END.length);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return segments;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// HTML escape utility (shared by NotificationManager, CodemanApp, and ralph-wizard.js)
|
|
233
|
+
const _htmlEscapeMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
234
|
+
const _htmlEscapePattern = /[&<>"']/g;
|
|
235
|
+
function escapeHtml(text) {
|
|
236
|
+
if (typeof text !== 'string') return '';
|
|
237
|
+
return text.replace(_htmlEscapePattern, (ch) => _htmlEscapeMap[ch]);
|
|
238
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
<script defer src="vendor/xterm-addon-fit.min.js"></script>
|
|
21
21
|
<script defer src="vendor/xterm-addon-webgl.min.js"></script>
|
|
22
22
|
<script defer src="vendor/xterm-addon-unicode11.min.js"></script>
|
|
23
|
+
<script defer src="vendor/xterm-zerolag-input.js?v=0.2.9"></script>
|
|
23
24
|
<!-- Synchronous mobile detection — runs before first paint to prevent panel flash -->
|
|
24
25
|
<script>if(window.innerWidth<768||(('ontouchstart' in window||navigator.maxTouchPoints>0)&&window.innerWidth<1024))document.documentElement.classList.add('mobile-init');</script>
|
|
25
26
|
<!-- Inline critical CSS for instant skeleton paint (before styles.css loads) -->
|
|
@@ -435,7 +436,7 @@
|
|
|
435
436
|
<div class="monitor-panel-header" id="monitorPanelHeader">
|
|
436
437
|
<div class="monitor-panel-title">Monitor</div>
|
|
437
438
|
<div class="monitor-panel-actions">
|
|
438
|
-
<button class="btn-toolbar btn-sm btn-danger" onclick="app.
|
|
439
|
+
<button class="btn-toolbar btn-sm btn-danger" onclick="app.killAllMuxSessions()" title="Kill all sessions and their tmux processes">Kill All</button>
|
|
439
440
|
<button class="btn-icon-sm" onclick="app.reconcileMuxSessions()" title="Refresh tmux sessions">↻</button>
|
|
440
441
|
<button class="btn-icon-sm" onclick="app.toggleMonitorDetach()" title="Detach panel" id="monitorDetachBtn">⧉</button>
|
|
441
442
|
<button class="btn-icon-sm" onclick="app.toggleMonitorPanel()" title="Toggle panel" id="monitorToggleBtn">▲</button>
|
|
@@ -1669,7 +1670,14 @@
|
|
|
1669
1670
|
<!-- Lines drawn dynamically -->
|
|
1670
1671
|
</svg>
|
|
1671
1672
|
|
|
1672
|
-
<script defer src="
|
|
1673
|
-
<script defer src="
|
|
1673
|
+
<script defer src="constants.js?v=0.2.9"></script>
|
|
1674
|
+
<script defer src="mobile-handlers.js?v=0.2.9"></script>
|
|
1675
|
+
<script defer src="voice-input.js?v=0.2.9"></script>
|
|
1676
|
+
<script defer src="notification-manager.js?v=0.2.9"></script>
|
|
1677
|
+
<script defer src="keyboard-accessory.js?v=0.2.9"></script>
|
|
1678
|
+
<script defer src="app.js?v=0.2.9"></script>
|
|
1679
|
+
<script defer src="ralph-wizard.js?v=0.2.9"></script>
|
|
1680
|
+
<script defer src="api-client.js?v=0.2.9"></script>
|
|
1681
|
+
<script defer src="subagent-windows.js?v=0.2.9"></script>
|
|
1674
1682
|
</body>
|
|
1675
1683
|
</html>
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
// Codeman — Keyboard accessory bar and focus trap for modals
|
|
2
|
+
// Loaded after mobile-handlers.js, before app.js
|
|
3
|
+
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Mobile Keyboard Accessory Bar
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* KeyboardAccessoryBar - Quick action buttons shown above keyboard when typing.
|
|
10
|
+
*/
|
|
11
|
+
const KeyboardAccessoryBar = {
|
|
12
|
+
element: null,
|
|
13
|
+
|
|
14
|
+
/** Create and inject the accessory bar */
|
|
15
|
+
init() {
|
|
16
|
+
// Only on mobile
|
|
17
|
+
if (!MobileDetection.isTouchDevice()) return;
|
|
18
|
+
|
|
19
|
+
// Create accessory bar element
|
|
20
|
+
this.element = document.createElement('div');
|
|
21
|
+
this.element.className = 'keyboard-accessory-bar';
|
|
22
|
+
this.element.innerHTML = `
|
|
23
|
+
<button class="accessory-btn accessory-btn-arrow" data-action="scroll-up" title="Arrow up">
|
|
24
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
25
|
+
<path d="M5 15l7-7 7 7"/>
|
|
26
|
+
</svg>
|
|
27
|
+
</button>
|
|
28
|
+
<button class="accessory-btn accessory-btn-arrow" data-action="scroll-down" title="Arrow down">
|
|
29
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
30
|
+
<path d="M19 9l-7 7-7-7"/>
|
|
31
|
+
</svg>
|
|
32
|
+
</button>
|
|
33
|
+
<button class="accessory-btn" data-action="init" title="/init">/init</button>
|
|
34
|
+
<button class="accessory-btn" data-action="clear" title="/clear">/clear</button>
|
|
35
|
+
<button class="accessory-btn" data-action="compact" title="/compact">/compact</button>
|
|
36
|
+
<button class="accessory-btn" data-action="paste" title="Paste from clipboard">
|
|
37
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
38
|
+
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/>
|
|
39
|
+
<rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
|
|
40
|
+
</svg>
|
|
41
|
+
</button>
|
|
42
|
+
<button class="accessory-btn accessory-btn-dismiss" data-action="dismiss" title="Dismiss keyboard">
|
|
43
|
+
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
|
|
44
|
+
<path d="M19 9l-7 7-7-7"/>
|
|
45
|
+
</svg>
|
|
46
|
+
</button>
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
// Add click handlers — preventDefault stops event from reaching terminal
|
|
50
|
+
this.element.addEventListener('click', (e) => {
|
|
51
|
+
const btn = e.target.closest('.accessory-btn');
|
|
52
|
+
if (!btn) return;
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
e.stopPropagation();
|
|
55
|
+
|
|
56
|
+
const action = btn.dataset.action;
|
|
57
|
+
this.handleAction(action, btn);
|
|
58
|
+
|
|
59
|
+
// Refocus terminal so keyboard stays open (tap blurs terminal → keyboard dismisses → toolbar shifts)
|
|
60
|
+
if ((action === 'scroll-up' || action === 'scroll-down') ||
|
|
61
|
+
((action === 'clear' || action === 'compact') && this._confirmAction)) {
|
|
62
|
+
if (typeof app !== 'undefined' && app.terminal) {
|
|
63
|
+
app.terminal.focus();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Insert before toolbar
|
|
69
|
+
const toolbar = document.querySelector('.toolbar');
|
|
70
|
+
if (toolbar && toolbar.parentNode) {
|
|
71
|
+
toolbar.parentNode.insertBefore(this.element, toolbar);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
_confirmTimer: null,
|
|
76
|
+
_confirmAction: null,
|
|
77
|
+
|
|
78
|
+
/** Handle accessory button actions */
|
|
79
|
+
handleAction(action, btn) {
|
|
80
|
+
if (typeof app === 'undefined' || !app.activeSessionId) return;
|
|
81
|
+
|
|
82
|
+
switch (action) {
|
|
83
|
+
case 'scroll-up':
|
|
84
|
+
this.sendKey('\x1b[A');
|
|
85
|
+
break;
|
|
86
|
+
case 'scroll-down':
|
|
87
|
+
this.sendKey('\x1b[B');
|
|
88
|
+
break;
|
|
89
|
+
case 'init':
|
|
90
|
+
this.sendCommand('/init');
|
|
91
|
+
break;
|
|
92
|
+
case 'clear':
|
|
93
|
+
case 'compact': {
|
|
94
|
+
// Require double-tap: first tap turns amber, second tap within 2s sends
|
|
95
|
+
const cmd = action === 'clear' ? '/clear' : '/compact';
|
|
96
|
+
if (this._confirmAction === action && this._confirmTimer) {
|
|
97
|
+
this.clearConfirm();
|
|
98
|
+
this.sendCommand(cmd);
|
|
99
|
+
} else {
|
|
100
|
+
this.setConfirm(action, btn);
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case 'paste':
|
|
105
|
+
this.pasteFromClipboard();
|
|
106
|
+
break;
|
|
107
|
+
case 'dismiss':
|
|
108
|
+
// Blur active element to dismiss keyboard
|
|
109
|
+
document.activeElement?.blur();
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
/** Enter confirm state: button turns amber for 2s waiting for second tap */
|
|
115
|
+
setConfirm(action, btn) {
|
|
116
|
+
this.clearConfirm();
|
|
117
|
+
this._confirmAction = action;
|
|
118
|
+
if (btn) {
|
|
119
|
+
btn.classList.add('confirming');
|
|
120
|
+
btn.dataset.origHtml = btn.innerHTML;
|
|
121
|
+
btn.textContent = 'Tap again';
|
|
122
|
+
}
|
|
123
|
+
this._confirmTimer = setTimeout(() => this.clearConfirm(), 2000);
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
/** Reset confirm state */
|
|
127
|
+
clearConfirm() {
|
|
128
|
+
if (this._confirmTimer) {
|
|
129
|
+
clearTimeout(this._confirmTimer);
|
|
130
|
+
this._confirmTimer = null;
|
|
131
|
+
}
|
|
132
|
+
if (this._confirmAction && this.element) {
|
|
133
|
+
const btn = this.element.querySelector(`[data-action="${this._confirmAction}"]`);
|
|
134
|
+
if (btn && btn.dataset.origHtml) {
|
|
135
|
+
btn.innerHTML = btn.dataset.origHtml;
|
|
136
|
+
delete btn.dataset.origHtml;
|
|
137
|
+
}
|
|
138
|
+
if (btn) btn.classList.remove('confirming');
|
|
139
|
+
}
|
|
140
|
+
this._confirmAction = null;
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
/** Send a slash command to the active session.
|
|
144
|
+
* Sends text and Enter separately so Ink processes them as distinct events. */
|
|
145
|
+
sendCommand(command) {
|
|
146
|
+
if (!app.activeSessionId) return;
|
|
147
|
+
// Send command text first (without Enter)
|
|
148
|
+
app.sendInput(command);
|
|
149
|
+
// Send Enter separately after a brief delay so Ink has time to process the text.
|
|
150
|
+
setTimeout(() => app.sendInput('\r'), 120);
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/** Send a special key (arrow, escape, etc.) directly to the PTY.
|
|
154
|
+
* Bypasses tmux send-keys -l (literal mode) since escape sequences
|
|
155
|
+
* must be written raw to be interpreted as key presses by Ink. */
|
|
156
|
+
sendKey(escapeSequence) {
|
|
157
|
+
if (!app.activeSessionId) return;
|
|
158
|
+
fetch(`/api/sessions/${app.activeSessionId}/input`, {
|
|
159
|
+
method: 'POST',
|
|
160
|
+
headers: { 'Content-Type': 'application/json' },
|
|
161
|
+
body: JSON.stringify({ input: escapeSequence })
|
|
162
|
+
}).catch(() => {});
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
/** Read clipboard and send contents as input */
|
|
166
|
+
/** Show a paste overlay with a textarea for iOS compatibility */
|
|
167
|
+
pasteFromClipboard() {
|
|
168
|
+
if (typeof app === 'undefined' || !app.activeSessionId) return;
|
|
169
|
+
|
|
170
|
+
// Create overlay
|
|
171
|
+
const overlay = document.createElement('div');
|
|
172
|
+
overlay.className = 'paste-overlay';
|
|
173
|
+
overlay.innerHTML = `
|
|
174
|
+
<div class="paste-dialog">
|
|
175
|
+
<textarea class="paste-textarea" placeholder="Long-press here and tap Paste"></textarea>
|
|
176
|
+
<div class="paste-actions">
|
|
177
|
+
<button class="paste-cancel">Cancel</button>
|
|
178
|
+
<button class="paste-send">Send</button>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
const textarea = overlay.querySelector('.paste-textarea');
|
|
184
|
+
const send = () => {
|
|
185
|
+
const text = textarea.value;
|
|
186
|
+
overlay.remove();
|
|
187
|
+
if (text) app.sendInput(text);
|
|
188
|
+
};
|
|
189
|
+
overlay.querySelector('.paste-cancel').addEventListener('click', () => overlay.remove());
|
|
190
|
+
overlay.querySelector('.paste-send').addEventListener('click', send);
|
|
191
|
+
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
|
192
|
+
|
|
193
|
+
document.body.appendChild(overlay);
|
|
194
|
+
textarea.focus();
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
/** Show the accessory bar */
|
|
198
|
+
show() {
|
|
199
|
+
if (this.element) {
|
|
200
|
+
this.element.classList.add('visible');
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
/** Hide the accessory bar */
|
|
205
|
+
hide() {
|
|
206
|
+
if (this.element) {
|
|
207
|
+
this.element.classList.remove('visible');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// ============================================================================
|
|
213
|
+
// Accessibility: Focus Trap for Modals
|
|
214
|
+
// ============================================================================
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* FocusTrap - Traps keyboard focus within an element (typically a modal).
|
|
218
|
+
* Saves the previously focused element and restores focus when deactivated.
|
|
219
|
+
*/
|
|
220
|
+
class FocusTrap {
|
|
221
|
+
constructor(element) {
|
|
222
|
+
this.element = element;
|
|
223
|
+
this.previouslyFocused = null;
|
|
224
|
+
this.boundHandleKeydown = this.handleKeydown.bind(this);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
activate() {
|
|
228
|
+
this.previouslyFocused = document.activeElement;
|
|
229
|
+
this.element.addEventListener('keydown', this.boundHandleKeydown);
|
|
230
|
+
|
|
231
|
+
// Focus first focusable element after a brief delay (for CSS transitions)
|
|
232
|
+
requestAnimationFrame(() => {
|
|
233
|
+
const focusable = this.getFocusableElements();
|
|
234
|
+
if (focusable.length) {
|
|
235
|
+
focusable[0].focus();
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
deactivate() {
|
|
241
|
+
this.element.removeEventListener('keydown', this.boundHandleKeydown);
|
|
242
|
+
if (this.previouslyFocused && typeof this.previouslyFocused.focus === 'function') {
|
|
243
|
+
this.previouslyFocused.focus();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
getFocusableElements() {
|
|
248
|
+
const selector = [
|
|
249
|
+
'button:not([disabled]):not([tabindex="-1"])',
|
|
250
|
+
'input:not([disabled]):not([tabindex="-1"])',
|
|
251
|
+
'select:not([disabled]):not([tabindex="-1"])',
|
|
252
|
+
'textarea:not([disabled]):not([tabindex="-1"])',
|
|
253
|
+
'a[href]:not([tabindex="-1"])',
|
|
254
|
+
'[tabindex]:not([tabindex="-1"]):not([disabled])'
|
|
255
|
+
].join(', ');
|
|
256
|
+
|
|
257
|
+
return [...this.element.querySelectorAll(selector)].filter(
|
|
258
|
+
el => el.offsetParent !== null // Exclude hidden elements
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
handleKeydown(e) {
|
|
263
|
+
if (e.key !== 'Tab') return;
|
|
264
|
+
|
|
265
|
+
const focusable = this.getFocusableElements();
|
|
266
|
+
if (focusable.length === 0) return;
|
|
267
|
+
|
|
268
|
+
const first = focusable[0];
|
|
269
|
+
const last = focusable[focusable.length - 1];
|
|
270
|
+
|
|
271
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
272
|
+
e.preventDefault();
|
|
273
|
+
last.focus();
|
|
274
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
275
|
+
e.preventDefault();
|
|
276
|
+
first.focus();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
Binary file
|
|
Binary file
|