remobi 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/actions/registry.ts","../../src/util/dom.ts","../../src/util/haptic.ts","../../src/controls/combo-picker.ts","../../src/util/keyboard.ts","../../src/util/terminal.ts","../../src/controls/floating-buttons.ts","../../src/controls/font-size.ts","../../src/controls/help.ts","../../src/gestures/lock.ts","../../src/gestures/scroll.ts","../../src/controls/scroll-buttons.ts","../../src/drawer/drawer.ts","../../src/gestures/pinch.ts","../../src/gestures/swipe.ts","../../src/hooks/registry.ts","../../src/reconnect.ts","../../src/theme/apply.ts","../../src/toolbar/toolbar.ts","../../src/viewport/landscape.ts","../../src/viewport/height.ts","../../src/index.ts"],"sourcesContent":["import type { ButtonAction, XTerminal } from '../types'\n\nexport interface ActionExecutionContext {\n\treadonly term: XTerminal\n\treadonly kbWasOpen: boolean\n\treadonly focusIfNeeded: () => void\n\treadonly sendText: (data: string) => Promise<void>\n\treadonly sendRawText?: (data: string) => Promise<void>\n\treadonly openDrawer?: () => void\n\treadonly openComboPicker?: (options: {\n\t\treadonly sendText: (data: string) => Promise<void>\n\t\treadonly focusIfNeeded: () => void\n\t}) => void\n\treadonly toggleCtrlModifier?: () => void\n}\n\ntype ActionHandler = (action: ButtonAction, context: ActionExecutionContext) => void | Promise<void>\n\nexport interface ActionRegistry {\n\tregister: (type: ButtonAction['type'], handler: ActionHandler) => void\n\texecute: (action: ButtonAction, context: ActionExecutionContext) => Promise<boolean>\n}\n\nexport function createActionRegistry(): ActionRegistry {\n\tconst handlers = new Map<ButtonAction['type'], ActionHandler>()\n\tlet sendQueue: Promise<void> = Promise.resolve()\n\n\tfunction register(type: ButtonAction['type'], handler: ActionHandler): void {\n\t\thandlers.set(type, handler)\n\t}\n\n\tasync function execute(action: ButtonAction, context: ActionExecutionContext): Promise<boolean> {\n\t\tconst handler = handlers.get(action.type)\n\t\tif (!handler) return false\n\n\t\tif (action.type === 'send') {\n\t\t\tconst current = sendQueue.then(async () => {\n\t\t\t\tawait handler(action, context)\n\t\t\t})\n\t\t\tsendQueue = current.catch(() => {})\n\t\t\tawait current\n\t\t\treturn true\n\t\t}\n\n\t\tif (action.type === 'paste') {\n\t\t\tawait sendQueue\n\t\t\tawait handler(action, context)\n\t\t\treturn true\n\t\t}\n\n\t\tawait handler(action, context)\n\t\treturn true\n\t}\n\n\treturn { register, execute }\n}\n\nexport function createDefaultActionRegistry(): ActionRegistry {\n\tconst registry = createActionRegistry()\n\tlet pasteQueue: Promise<void> = Promise.resolve()\n\n\tregistry.register('send', (action, context) => {\n\t\tif (action.type !== 'send') return\n\t\treturn context.sendText(action.data).then(() => context.focusIfNeeded())\n\t})\n\n\tregistry.register('paste', (_action, context) => {\n\t\tif (!navigator.clipboard || typeof navigator.clipboard.readText !== 'function') {\n\t\t\tcontext.focusIfNeeded()\n\t\t\treturn\n\t\t}\n\n\t\tconst runPaste = async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\tconst text = await navigator.clipboard.readText()\n\t\t\t\tif (!text) return\n\t\t\t\tif (context.sendRawText) {\n\t\t\t\t\tawait context.sendRawText(text)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tawait context.sendText(text)\n\t\t\t} catch {\n\t\t\t\t// Clipboard access may fail due to permissions or browser constraints.\n\t\t\t\t// Keep behaviour fail-safe and restore focus without surfacing runtime errors.\n\t\t\t} finally {\n\t\t\t\tcontext.focusIfNeeded()\n\t\t\t}\n\t\t}\n\n\t\tconst current = pasteQueue.then(runPaste)\n\t\tpasteQueue = current.catch(() => {})\n\t\treturn current\n\t})\n\n\tregistry.register('ctrl-modifier', (_action, context) => {\n\t\tif (context.toggleCtrlModifier) {\n\t\t\tcontext.toggleCtrlModifier()\n\t\t} else {\n\t\t\tcontext.focusIfNeeded()\n\t\t}\n\t})\n\n\tregistry.register('drawer-toggle', (_action, context) => {\n\t\tif (context.openDrawer) {\n\t\t\tcontext.openDrawer()\n\t\t} else {\n\t\t\tcontext.focusIfNeeded()\n\t\t}\n\t})\n\n\tregistry.register('combo-picker', (_action, context) => {\n\t\tif (context.openComboPicker) {\n\t\t\tcontext.openComboPicker({\n\t\t\t\tsendText: async (data: string) => {\n\t\t\t\t\tawait registry.execute(\n\t\t\t\t\t\t{ type: 'send', data },\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t...context,\n\t\t\t\t\t\t\tsendText: context.sendRawText ?? context.sendText,\n\t\t\t\t\t\t},\n\t\t\t\t\t)\n\t\t\t\t},\n\t\t\t\tfocusIfNeeded: context.focusIfNeeded,\n\t\t\t})\n\t\t} else {\n\t\t\tcontext.focusIfNeeded()\n\t\t}\n\t})\n\n\treturn registry\n}\n","/** Create an element with optional attributes and children */\nexport function el<K extends keyof HTMLElementTagNameMap>(\n\ttag: K,\n\tattrs?: Record<string, string>,\n\t...children: Array<string | HTMLElement>\n): HTMLElementTagNameMap[K] {\n\tconst element = document.createElement(tag)\n\tif (attrs) {\n\t\tfor (const [key, value] of Object.entries(attrs)) {\n\t\t\telement.setAttribute(key, value)\n\t\t}\n\t}\n\tfor (const child of children) {\n\t\tif (typeof child === 'string') {\n\t\t\telement.appendChild(document.createTextNode(child))\n\t\t} else {\n\t\t\telement.appendChild(child)\n\t\t}\n\t}\n\treturn element\n}\n\n/** Inject a <style> block into <head> */\nexport function injectStyles(css: string): HTMLStyleElement {\n\tconst style = el('style')\n\tstyle.textContent = css\n\tdocument.head.appendChild(style)\n\treturn style\n}\n\n/** Create a button with label and aria-label */\nexport function btn(label: string, ariaLabel?: string): HTMLButtonElement {\n\tconst button = el('button')\n\tbutton.textContent = label\n\tif (ariaLabel) {\n\t\tbutton.setAttribute('aria-label', ariaLabel)\n\t}\n\treturn button\n}\n","/** Short haptic vibration — no-op on devices without vibration API */\nexport function haptic(): void {\n\tif (navigator.vibrate) {\n\t\tnavigator.vibrate(10)\n\t}\n}\n","import { el } from '../util/dom'\nimport { haptic } from '../util/haptic'\n\ninterface ComboDispatch {\n\treadonly sendText: (data: string) => Promise<void>\n\treadonly focusIfNeeded: () => void\n}\n\ntype ComboParseResult =\n\t| { readonly ok: true; readonly data: string }\n\t| { readonly ok: false; readonly error: string }\n\ninterface ComboTokens {\n\treadonly modifiers: readonly string[]\n\treadonly key: string\n}\n\nconst NAMED_KEYS = [\n\t'pagedown',\n\t'pageup',\n\t'return',\n\t'escape',\n\t'backspace',\n\t'delete',\n\t'enter',\n\t'space',\n\t'tab',\n\t'home',\n\t'end',\n\t'left',\n\t'right',\n\t'down',\n\t'up',\n\t'pgdn',\n\t'pgup',\n\t'del',\n\t'esc',\n\t'bs',\n] as const\n\nfunction parseComboTokens(value: string): ComboTokens | null {\n\tconst trimmed = value.trim()\n\tif (trimmed.length === 0) {\n\t\treturn null\n\t}\n\n\tlet keyToken: string | null = null\n\tlet prefix = ''\n\n\tfor (const key of NAMED_KEYS) {\n\t\tconst pattern = new RegExp(`(?:^|[+\\\\-\\\\s])(${key})$`, 'i')\n\t\tconst match = trimmed.match(pattern)\n\t\tif (!match || match.index === undefined) continue\n\n\t\tconst matchedKey = match[1]\n\t\tif (!matchedKey) continue\n\t\tconst keyStart = match.index + match[0].length - matchedKey.length\n\t\tkeyToken = matchedKey\n\t\tprefix = trimmed.slice(0, keyStart)\n\t\tbreak\n\t}\n\n\tif (!keyToken) {\n\t\tkeyToken = trimmed[trimmed.length - 1] ?? ''\n\t\tprefix = trimmed.slice(0, -1)\n\t}\n\n\tconst modifiers = prefix\n\t\t.split(/[+\\-\\s]+/)\n\t\t.map((token) => token.trim())\n\t\t.filter((token) => token.length > 0)\n\n\treturn { modifiers, key: keyToken }\n}\n\nfunction resolveBaseKey(key: string, keyLower: string): ComboParseResult {\n\tif (key.length === 1) {\n\t\treturn { ok: true, data: key }\n\t}\n\n\tif (keyLower === 'enter' || keyLower === 'return') return { ok: true, data: '\\r' }\n\tif (keyLower === 'tab') return { ok: true, data: '\\t' }\n\tif (keyLower === 'space') return { ok: true, data: ' ' }\n\tif (keyLower === 'esc' || keyLower === 'escape') return { ok: true, data: '\\x1b' }\n\tif (keyLower === 'backspace' || keyLower === 'bs') return { ok: true, data: '\\x7f' }\n\tif (keyLower === 'delete' || keyLower === 'del') return { ok: true, data: '\\x1b[3~' }\n\tif (keyLower === 'up') return { ok: true, data: '\\x1b[A' }\n\tif (keyLower === 'down') return { ok: true, data: '\\x1b[B' }\n\tif (keyLower === 'right') return { ok: true, data: '\\x1b[C' }\n\tif (keyLower === 'left') return { ok: true, data: '\\x1b[D' }\n\tif (keyLower === 'home') return { ok: true, data: '\\x1b[H' }\n\tif (keyLower === 'end') return { ok: true, data: '\\x1b[F' }\n\tif (keyLower === 'pageup' || keyLower === 'pgup') return { ok: true, data: '\\x1b[5~' }\n\tif (keyLower === 'pagedown' || keyLower === 'pgdn') return { ok: true, data: '\\x1b[6~' }\n\n\treturn {\n\t\tok: false,\n\t\terror: 'Unknown key. Try one character, Enter, Tab, Space, Esc, arrows, Home/End, PgUp/PgDn.',\n\t}\n}\n\nfunction applyCtrl(base: string, key: string, keyLower: string): ComboParseResult {\n\tif (base.length !== 1) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: 'Ctrl supports single characters (for Enter use M-Enter).',\n\t\t}\n\t}\n\n\tif (keyLower === 'space') return { ok: true, data: '\\x00' }\n\tif (key.length !== 1) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: 'Unsupported Ctrl combo for this key.',\n\t\t}\n\t}\n\n\tif (key === '[') return { ok: true, data: '\\x1b' }\n\tif (key === '\\\\') return { ok: true, data: '\\x1c' }\n\tif (key === ']') return { ok: true, data: '\\x1d' }\n\tif (key === '6') return { ok: true, data: '\\x1e' }\n\tif (key === '-' || key === '/') return { ok: true, data: '\\x1f' }\n\tif (key === '8') return { ok: true, data: '\\x7f' }\n\n\tconst code = key.charCodeAt(0)\n\tif ((code >= 65 && code <= 90) || (code >= 97 && code <= 122)) {\n\t\treturn { ok: true, data: String.fromCharCode(code & 0x1f) }\n\t}\n\n\treturn {\n\t\tok: false,\n\t\terror: 'Unsupported Ctrl combo for this key.',\n\t}\n}\n\nexport function parseComboInput(value: string): ComboParseResult {\n\tconst tokens = parseComboTokens(value)\n\tif (!tokens) {\n\t\treturn { ok: false, error: 'Type a combo like C-s, M-Enter, or C-[.' }\n\t}\n\n\tlet ctrl = false\n\tlet alt = false\n\n\tfor (const modifier of tokens.modifiers) {\n\t\tconst token = modifier.toLowerCase()\n\t\tif (token === 'c' || token === 'ctrl' || token === 'control') {\n\t\t\tctrl = true\n\t\t\tcontinue\n\t\t}\n\t\tif (token === 'm' || token === 'meta' || token === 'alt' || token === 'a') {\n\t\t\talt = true\n\t\t\tcontinue\n\t\t}\n\t\tif (token === 's' || token === 'shift') {\n\t\t\tcontinue\n\t\t}\n\t\treturn { ok: false, error: `Unknown modifier: ${modifier}` }\n\t}\n\n\tconst keyToken = tokens.key\n\tif (!keyToken) {\n\t\treturn { ok: false, error: 'Missing key in combo.' }\n\t}\n\n\tconst keyLower = keyToken.toLowerCase()\n\tconst base = resolveBaseKey(keyToken, keyLower)\n\tif (!base.ok) return base\n\n\tlet data = base.data\n\tif (ctrl) {\n\t\tconst next = applyCtrl(data, keyToken, keyLower)\n\t\tif (!next.ok) return next\n\t\tdata = next.data\n\t}\n\n\tif (alt) {\n\t\tdata = `\\x1b${data}`\n\t}\n\n\treturn { ok: true, data }\n}\n\ninterface ComboPickerResult {\n\treadonly element: HTMLDivElement\n\treadonly open: (dispatch: ComboDispatch) => void\n\treadonly close: () => void\n}\n\nexport function createComboPicker(): ComboPickerResult {\n\tconst backdrop = el('div', { id: 'wt-combo-backdrop' })\n\tconst panel = el('div', { id: 'wt-combo-panel' })\n\tconst title = el('h3')\n\ttitle.textContent = 'Send combo'\n\tconst description = el('p')\n\tdescription.textContent = 'Examples: C-s, C-[, M-Enter, Alt-x'\n\tconst input = el('input', {\n\t\ttype: 'text',\n\t\tplaceholder: 'Combo',\n\t\t'aria-label': 'Combo input',\n\t\tautocomplete: 'off',\n\t\tautocorrect: 'off',\n\t\tautocapitalize: 'off',\n\t\tspellcheck: 'false',\n\t})\n\tconst error = el('p', { class: 'wt-combo-error' })\n\tconst actions = el('div', { class: 'wt-combo-actions' })\n\tconst cancelButton = el('button', { type: 'button' }, 'Cancel')\n\tconst sendButton = el('button', { type: 'button' }, 'Send')\n\n\tactions.appendChild(cancelButton)\n\tactions.appendChild(sendButton)\n\tpanel.appendChild(title)\n\tpanel.appendChild(description)\n\tpanel.appendChild(input)\n\tpanel.appendChild(error)\n\tpanel.appendChild(actions)\n\tbackdrop.appendChild(panel)\n\n\tlet currentDispatch: ComboDispatch | null = null\n\n\tfunction clearError(): void {\n\t\terror.textContent = ''\n\t}\n\n\tfunction setError(message: string): void {\n\t\terror.textContent = message\n\t}\n\n\tfunction closeAndFocus(): void {\n\t\tconst dispatch = currentDispatch\n\t\tbackdrop.style.display = 'none'\n\t\tcurrentDispatch = null\n\t\tclearError()\n\t\tinput.value = ''\n\t\tif (dispatch) {\n\t\t\tdispatch.focusIfNeeded()\n\t\t}\n\t}\n\n\tasync function submit(): Promise<void> {\n\t\tconst dispatch = currentDispatch\n\t\tif (!dispatch) return\n\n\t\tconst parsed = parseComboInput(input.value)\n\t\tif (!parsed.ok) {\n\t\t\tsetError(parsed.error)\n\t\t\treturn\n\t\t}\n\n\t\tbackdrop.style.display = 'none'\n\t\tcurrentDispatch = null\n\t\tclearError()\n\t\tinput.value = ''\n\n\t\ttry {\n\t\t\tawait dispatch.sendText(parsed.data)\n\t\t} catch (errorValue) {\n\t\t\tconsole.error('remobi: combo send failed', errorValue)\n\t\t} finally {\n\t\t\tdispatch.focusIfNeeded()\n\t\t}\n\t}\n\n\tfunction open(dispatch: ComboDispatch): void {\n\t\tcurrentDispatch = dispatch\n\t\tclearError()\n\t\tinput.value = ''\n\t\tbackdrop.style.display = 'flex'\n\t\tsetTimeout(() => input.focus(), 0)\n\t}\n\n\tfunction close(): void {\n\t\tcloseAndFocus()\n\t}\n\n\tbackdrop.addEventListener('click', (event: Event) => {\n\t\tif (event.target !== backdrop) return\n\t\thaptic()\n\t\tcloseAndFocus()\n\t})\n\n\tcancelButton.addEventListener('click', (event: Event) => {\n\t\tevent.preventDefault()\n\t\thaptic()\n\t\tcloseAndFocus()\n\t})\n\n\tsendButton.addEventListener('click', (event: Event) => {\n\t\tevent.preventDefault()\n\t\thaptic()\n\t\tvoid submit()\n\t})\n\n\tinput.addEventListener('keydown', (event: KeyboardEvent) => {\n\t\tif (event.key === 'Enter') {\n\t\t\tevent.preventDefault()\n\t\t\thaptic()\n\t\t\tvoid submit()\n\t\t\treturn\n\t\t}\n\t\tif (event.key === 'Escape') {\n\t\t\tevent.preventDefault()\n\t\t\thaptic()\n\t\t\tcloseAndFocus()\n\t\t}\n\t})\n\n\treturn { element: backdrop, open, close }\n}\n","import type { XTerminal } from '../types'\n\n/** Threshold in pixels — if the gap between innerHeight and viewport height exceeds this, the keyboard is open */\nexport const KB_THRESHOLD = 150\n\n/** Check whether the virtual keyboard appears to be open */\nexport function isKeyboardOpen(): boolean {\n\tconst vp = window.visualViewport\n\tif (!vp) return false\n\treturn window.innerHeight - vp.height > KB_THRESHOLD\n}\n\n/** Focus terminal only if the keyboard was already visible */\nexport function conditionalFocus(term: XTerminal, kbWasOpen: boolean): void {\n\tif (kbWasOpen) {\n\t\tterm.focus()\n\t}\n}\n","import type { XTerminal } from '../types'\n\n/** Send data to the terminal as if the user typed it */\nexport function sendData(term: XTerminal, data: string): void {\n\tterm.input(data, true)\n}\n\n/** Trigger xterm resize via window resize event */\nexport function resizeTerm(): void {\n\twindow.dispatchEvent(new Event('resize'))\n}\n\n/**\n * Wait for `window.term` to become available (ttyd sets it).\n * Resolves with the terminal instance, rejects after maxRetries (default 100 = 10s).\n */\nexport function waitForTerm(maxRetries = 100): Promise<XTerminal> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet attempts = 0\n\t\tfunction check(): void {\n\t\t\tif (window.term) {\n\t\t\t\tresolve(window.term)\n\t\t\t} else if (attempts >= maxRetries) {\n\t\t\t\treject(new Error(`waitForTerm: window.term not available after ${maxRetries * 100}ms`))\n\t\t\t} else {\n\t\t\t\tattempts += 1\n\t\t\t\tsetTimeout(check, 100)\n\t\t\t}\n\t\t}\n\t\tcheck()\n\t})\n}\n","import type { ActionRegistry } from '../actions/registry'\nimport type { HookRegistry } from '../hooks/registry'\nimport type { ControlButton, FloatingButtonGroup, RemobiConfig, XTerminal } from '../types'\nimport { el } from '../util/dom'\nimport { haptic } from '../util/haptic'\nimport { conditionalFocus, isKeyboardOpen } from '../util/keyboard'\nimport { sendData } from '../util/terminal'\n\nfunction createGroupButton(\n\tterm: XTerminal,\n\tdef: ControlButton,\n\tconfig: RemobiConfig,\n\thooks: HookRegistry,\n\tactions: ActionRegistry,\n\topenDrawer: (() => void) | undefined,\n\topenComboPicker:\n\t\t| ((options: {\n\t\t\t\treadonly sendText: (data: string) => Promise<void>\n\t\t\t\treadonly focusIfNeeded: () => void\n\t\t }) => void)\n\t\t| undefined,\n): HTMLButtonElement {\n\tconst button = el('button')\n\tbutton.textContent = def.label\n\tbutton.setAttribute('aria-label', def.description)\n\n\tbutton.addEventListener('click', (e: Event) => {\n\t\te.preventDefault()\n\t\tconst kbWasOpen = isKeyboardOpen()\n\t\thaptic()\n\n\t\tasync function sendWithHooks(data: string): Promise<void> {\n\t\t\tconst before = await hooks.runBeforeSendData({\n\t\t\t\tterm,\n\t\t\t\tconfig,\n\t\t\t\tsource: 'floating-buttons',\n\t\t\t\tactionType: def.action.type,\n\t\t\t\tkbWasOpen,\n\t\t\t\tdata,\n\t\t\t})\n\t\t\tif (before.blocked) return\n\n\t\t\tsendData(term, before.data)\n\t\t\tawait hooks.runAfterSendData({\n\t\t\t\tterm,\n\t\t\t\tconfig,\n\t\t\t\tsource: 'floating-buttons',\n\t\t\t\tactionType: def.action.type,\n\t\t\t\tkbWasOpen,\n\t\t\t\tdata: before.data,\n\t\t\t})\n\t\t}\n\n\t\tvoid actions\n\t\t\t.execute(def.action, {\n\t\t\t\tterm,\n\t\t\t\tkbWasOpen,\n\t\t\t\tfocusIfNeeded: () => conditionalFocus(term, kbWasOpen),\n\t\t\t\tsendText: sendWithHooks,\n\t\t\t\tsendRawText: sendWithHooks,\n\t\t\t\topenDrawer,\n\t\t\t\topenComboPicker,\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tconsole.error('remobi: floating button action failed', error)\n\t\t\t\tconditionalFocus(term, kbWasOpen)\n\t\t\t})\n\t})\n\n\treturn button\n}\n\n/**\n * Create one container element per floating button group.\n * Each group is positioned via CSS classes (`wt-floating-group`, `wt-floating-${position}`)\n * and rendered as a row or column depending on `direction` (default: row).\n */\nexport function createFloatingButtons(\n\tterm: XTerminal,\n\tgroups: readonly FloatingButtonGroup[],\n\tconfig: RemobiConfig,\n\thooks: HookRegistry,\n\tactions: ActionRegistry,\n\topenDrawer?: () => void,\n\topenComboPicker?: (options: {\n\t\treadonly sendText: (data: string) => Promise<void>\n\t\treadonly focusIfNeeded: () => void\n\t}) => void,\n): { elements: HTMLDivElement[] } {\n\tconst elements: HTMLDivElement[] = []\n\n\tfor (const group of groups) {\n\t\tconst container = el('div', {\n\t\t\tclass: `wt-floating-group wt-floating-${group.position}${group.direction === 'column' ? ' wt-floating-column' : ''}`,\n\t\t})\n\n\t\tfor (const def of group.buttons) {\n\t\t\tcontainer.appendChild(\n\t\t\t\tcreateGroupButton(term, def, config, hooks, actions, openDrawer, openComboPicker),\n\t\t\t)\n\t\t}\n\n\t\telements.push(container)\n\t}\n\n\treturn { elements }\n}\n","import type { FontConfig, XTerminal } from '../types'\nimport { btn, el } from '../util/dom'\nimport { haptic } from '../util/haptic'\nimport { resizeTerm } from '../util/terminal'\n\n/** Change terminal font size by delta, clamped to config range */\nfunction changeFontSize(term: XTerminal, delta: number, font: FontConfig): void {\n\tconst current = term.options.fontSize\n\tconst next = Math.max(font.sizeRange[0], Math.min(font.sizeRange[1], current + delta))\n\tif (next !== current) {\n\t\tterm.options.fontSize = next\n\t\tresizeTerm()\n\t}\n}\n\ninterface FontControlsResult {\n\treadonly element: HTMLDivElement\n\treadonly helpButton: HTMLButtonElement\n}\n\n/** Create the font size controls (-, +) and help button */\nexport function createFontControls(term: XTerminal, font: FontConfig): FontControlsResult {\n\tconst container = el('div', { id: 'wt-font-controls' })\n\n\tconst btnMinus = btn('\\u2212', 'Decrease font size')\n\tconst btnPlus = btn('+', 'Increase font size')\n\tconst btnHelp = btn('?', 'Help')\n\n\tcontainer.appendChild(btnMinus)\n\tcontainer.appendChild(btnPlus)\n\tcontainer.appendChild(btnHelp)\n\n\tbtnMinus.addEventListener('click', (e: Event) => {\n\t\te.preventDefault()\n\t\thaptic()\n\t\tchangeFontSize(term, -2, font)\n\t})\n\n\tbtnPlus.addEventListener('click', (e: Event) => {\n\t\te.preventDefault()\n\t\thaptic()\n\t\tchangeFontSize(term, 2, font)\n\t})\n\n\treturn { element: container, helpButton: btnHelp }\n}\n","import type { ControlButton, FloatingButtonGroup, RemobiConfig, XTerminal } from '../types'\nimport { el } from '../util/dom'\nimport { haptic } from '../util/haptic'\nimport { conditionalFocus, isKeyboardOpen } from '../util/keyboard'\n\n/** Create a table row with two cells — textContent auto-escapes */\nfunction row(left: string, right: string): HTMLTableRowElement {\n\treturn el('tr', {}, el('td', {}, left), el('td', {}, right))\n}\n\nfunction renderButtonTable(title: string, buttons: readonly ControlButton[]): DocumentFragment {\n\tconst frag = document.createDocumentFragment()\n\tfrag.appendChild(el('h2', {}, title))\n\tconst table = el('table')\n\tfor (const button of buttons) {\n\t\ttable.appendChild(\n\t\t\trow(button.label || button.id || 'Unnamed', button.description || 'No description'),\n\t\t)\n\t}\n\tfrag.appendChild(table)\n\treturn frag\n}\n\nfunction renderGestures(config: RemobiConfig): DocumentFragment {\n\tconst frag = document.createDocumentFragment()\n\tfrag.appendChild(el('h2', {}, 'Gestures'))\n\tconst table = el('table')\n\n\tif (config.gestures.swipe.enabled) {\n\t\ttable.appendChild(row('Swipe right', config.gestures.swipe.rightLabel))\n\t\ttable.appendChild(row('Swipe left', config.gestures.swipe.leftLabel))\n\t}\n\n\tif (config.gestures.pinch.enabled) {\n\t\ttable.appendChild(row('Pinch in/out', 'Decrease/increase font size'))\n\t}\n\n\tif (config.gestures.scroll.enabled) {\n\t\tif (config.gestures.scroll.strategy === 'wheel') {\n\t\t\ttable.appendChild(row('Finger drag', 'Send wheel scroll events to terminal apps'))\n\t\t\ttable.appendChild(row('Side \\u25B2 \\u25BC', 'Send wheel-up / wheel-down at terminal centre'))\n\t\t} else {\n\t\t\ttable.appendChild(row('Finger drag', 'Send PageUp / PageDown keys'))\n\t\t\ttable.appendChild(row('Side \\u25B2 \\u25BC', 'Send PageUp / PageDown keys'))\n\t\t}\n\t}\n\n\tif (table.rows.length === 0) {\n\t\ttable.appendChild(row('None', 'All gestures are disabled in config'))\n\t}\n\n\tfrag.appendChild(table)\n\treturn frag\n}\n\n/** Build the help overlay content as a DocumentFragment — no innerHTML */\nfunction buildHelpContent(config: RemobiConfig): DocumentFragment {\n\tconst topRightButtons: readonly ControlButton[] = [\n\t\t{\n\t\t\tid: 'font-size',\n\t\t\tlabel: '\\u2212 / +',\n\t\t\tdescription: 'Decrease / increase font size',\n\t\t\taction: { type: 'send', data: '' },\n\t\t},\n\t\t{\n\t\t\tid: 'help',\n\t\t\tlabel: '?',\n\t\t\tdescription: 'Open this help screen',\n\t\t\taction: { type: 'send', data: '' },\n\t\t},\n\t]\n\n\tconst frag = document.createDocumentFragment()\n\n\tconst closeBtn = el('button', { class: 'wt-help-close' }, '\\u00D7')\n\tfrag.appendChild(closeBtn)\n\n\tfrag.appendChild(renderButtonTable('Toolbar \\u2014 Row 1', config.toolbar.row1))\n\tfrag.appendChild(renderButtonTable('Toolbar \\u2014 Row 2', config.toolbar.row2))\n\tfrag.appendChild(renderButtonTable('Drawer Buttons', config.drawer.buttons))\n\tfrag.appendChild(renderGestures(config))\n\tfrag.appendChild(renderButtonTable('Top-Right Controls', topRightButtons))\n\n\tif (config.floatingButtons.length > 0) {\n\t\tconst groups: readonly FloatingButtonGroup[] = config.floatingButtons\n\t\tif (groups.length === 1 && groups[0] !== undefined) {\n\t\t\tfrag.appendChild(renderButtonTable('Floating Buttons', groups[0].buttons))\n\t\t} else {\n\t\t\tfor (const group of groups) {\n\t\t\t\tfrag.appendChild(renderButtonTable(`Floating Buttons (${group.position})`, group.buttons))\n\t\t\t}\n\t\t}\n\t}\n\n\treturn frag\n}\n\ninterface HelpOverlayResult {\n\treadonly element: HTMLDivElement\n\treadonly open: () => void\n\treadonly close: () => void\n}\n\n/** Create the help overlay and wire the help button */\nexport function createHelpOverlay(\n\tterm: XTerminal,\n\thelpButton: HTMLButtonElement,\n\tconfig: RemobiConfig,\n): HelpOverlayResult {\n\tconst overlay = el('div', { id: 'wt-help' })\n\toverlay.appendChild(buildHelpContent(config))\n\n\tfunction open(): void {\n\t\toverlay.style.display = 'block'\n\t}\n\n\tfunction close(): void {\n\t\toverlay.style.display = 'none'\n\t}\n\n\toverlay.addEventListener('click', (e: Event) => {\n\t\tconst target = e.target\n\t\tif (!(target instanceof HTMLElement)) return\n\t\tif (target === overlay || target.classList.contains('wt-help-close')) {\n\t\t\tconst kbWasOpen = isKeyboardOpen()\n\t\t\thaptic()\n\t\t\tclose()\n\t\t\tconditionalFocus(term, kbWasOpen)\n\t\t}\n\t})\n\n\thelpButton.addEventListener('click', (e: Event) => {\n\t\te.preventDefault()\n\t\thaptic()\n\t\topen()\n\t})\n\n\treturn { element: overlay, open, close }\n}\n","/** Gesture types that can claim the lock */\nexport type GestureType = 'none' | 'pinch' | 'scroll'\n\n/** Shared lock so only one gesture owns two-finger input at a time */\nexport interface GestureLock {\n\tcurrent: GestureType\n}\n\n/** Create a gesture lock in the unclaimed state */\nexport function createGestureLock(): GestureLock {\n\treturn { current: 'none' }\n}\n\n/** Try to claim the lock. Succeeds only if no gesture owns it yet. */\nexport function tryLock(lock: GestureLock, type: GestureType): boolean {\n\tif (lock.current !== 'none') return false\n\tlock.current = type\n\treturn true\n}\n\n/** Release the lock back to unclaimed */\nexport function resetLock(lock: GestureLock): void {\n\tlock.current = 'none'\n}\n","import type { ScrollConfig, XTerminal } from '../types'\nimport { sendData } from '../util/terminal'\nimport type { GestureLock } from './lock'\nimport { resetLock, tryLock } from './lock'\n\n/** Average Y coordinate of two touches */\nexport function averageY(t0: { clientY: number }, t1: { clientY: number }): number {\n\treturn (t0.clientY + t1.clientY) / 2\n}\n\n/** SGR mouse wheel escape sequence for a given direction */\nexport function scrollSeq(direction: 'up' | 'down', x: number, y: number): string {\n\tconst code = direction === 'up' ? 64 : 65\n\treturn `\\x1b[\\x3c${code};${x};${y}M`\n}\n\n/** Page navigation key sequence for a given direction */\nexport function pageSeq(direction: 'up' | 'down'): string {\n\treturn direction === 'up' ? '\\x1b[5~' : '\\x1b[6~'\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n\treturn Math.min(max, Math.max(min, value))\n}\n\nfunction terminalGrid(screenRect: DOMRect, term: XTerminal): { cols: number; rows: number } {\n\tconst colsFromTerm = term.cols\n\tconst rowsFromTerm = term.rows\n\tif (typeof colsFromTerm === 'number' && typeof rowsFromTerm === 'number') {\n\t\tif (colsFromTerm > 0 && rowsFromTerm > 0) {\n\t\t\treturn { cols: Math.round(colsFromTerm), rows: Math.round(rowsFromTerm) }\n\t\t}\n\t}\n\n\tconst measure = document.querySelector('.xterm-char-measure-element')\n\tif (measure instanceof HTMLElement) {\n\t\tconst measureRect = measure.getBoundingClientRect()\n\t\tif (measureRect.width > 0 && measureRect.height > 0) {\n\t\t\tconst cols = Math.max(1, Math.round(screenRect.width / measureRect.width))\n\t\t\tconst rows = Math.max(1, Math.round(screenRect.height / measureRect.height))\n\t\t\treturn { cols, rows }\n\t\t}\n\t}\n\n\treturn { cols: 80, rows: 24 }\n}\n\nfunction touchToCell(touch: Touch, screen: HTMLElement, term: XTerminal): { x: number; y: number } {\n\tconst rect = screen.getBoundingClientRect()\n\tconst { cols, rows } = terminalGrid(rect, term)\n\tconst width = Math.max(1, rect.width)\n\tconst height = Math.max(1, rect.height)\n\tconst relX = clamp(touch.clientX - rect.left, 0, width)\n\tconst relY = clamp(touch.clientY - rect.top, 0, height)\n\tconst x = clamp(Math.floor((relX / width) * cols) + 1, 1, cols)\n\tconst y = clamp(Math.floor((relY / height) * rows) + 1, 1, rows)\n\treturn { x, y }\n}\n\n/** Attach single-finger vertical scroll to the xterm screen */\nexport function attachScrollGesture(\n\tterm: XTerminal,\n\tconfig: ScrollConfig,\n\tlock: GestureLock,\n\tisDrawerOpen: () => boolean,\n): void {\n\tlet startY = 0\n\tlet lastY = 0\n\tlet accDelta = 0\n\tlet lastWheelAt = 0\n\tlet screenEl: HTMLElement | null = null\n\n\tfunction onTouchStart(e: Event): void {\n\t\tif (!(e instanceof TouchEvent)) return\n\t\tif (e.touches.length === 1) {\n\t\t\tconst t = e.touches[0]\n\t\t\tif (!t) return\n\t\t\tstartY = t.clientY\n\t\t\tlastY = t.clientY\n\t\t\taccDelta = 0\n\t\t}\n\t}\n\n\tfunction onTouchMove(e: Event): void {\n\t\tif (!(e instanceof TouchEvent)) return\n\t\tif (e.touches.length !== 1 || isDrawerOpen()) return\n\t\tconst t = e.touches[0]\n\t\tif (!t) return\n\n\t\tconst y = t.clientY\n\t\tconst totalDy = y - startY\n\n\t\t// Try to claim lock if unclaimed\n\t\tif (lock.current === 'none' && Math.abs(totalDy) > config.sensitivity) {\n\t\t\tif (!tryLock(lock, 'scroll')) return\n\t\t}\n\n\t\t// Only process if we own the lock\n\t\tif (lock.current !== 'scroll') return\n\n\t\te.preventDefault()\n\n\t\tconst moveDy = y - lastY\n\t\tlastY = y\n\t\taccDelta += moveDy\n\n\t\t// Send one scroll action per sensitivity-worth of pixels\n\t\twhile (Math.abs(accDelta) >= config.sensitivity) {\n\t\t\tconst dir = accDelta < 0 ? 'down' : 'up'\n\n\t\t\tif (config.strategy === 'keys') {\n\t\t\t\tsendData(term, pageSeq(dir))\n\t\t\t} else {\n\t\t\t\tconst now = Date.now()\n\t\t\t\tif (now - lastWheelAt < config.wheelIntervalMs) break\n\t\t\t\tlastWheelAt = now\n\n\t\t\t\tconst screen = screenEl\n\t\t\t\tif (!screen) break\n\t\t\t\tconst { x, y: row } = touchToCell(t, screen, term)\n\t\t\t\tsendData(term, scrollSeq(dir, x, row))\n\t\t\t}\n\n\t\t\taccDelta -= (accDelta < 0 ? -1 : 1) * config.sensitivity\n\t\t}\n\t}\n\n\tfunction onTouchEnd(e: Event): void {\n\t\tif (!(e instanceof TouchEvent)) return\n\t\tif (lock.current === 'scroll') {\n\t\t\tresetLock(lock)\n\t\t}\n\t}\n\n\tfunction attach(): void {\n\t\tconst screen = document.querySelector('.xterm-screen')\n\t\tif (!(screen instanceof HTMLElement)) {\n\t\t\tsetTimeout(attach, 200)\n\t\t\treturn\n\t\t}\n\n\t\tscreenEl = screen\n\t\tscreen.addEventListener('touchstart', onTouchStart, {\n\t\t\tpassive: true,\n\t\t})\n\t\tscreen.addEventListener('touchmove', onTouchMove, {\n\t\t\tpassive: false,\n\t\t})\n\t\tscreen.addEventListener('touchend', onTouchEnd, { passive: true })\n\t\tscreen.addEventListener('touchcancel', onTouchEnd, { passive: true })\n\t}\n\n\tattach()\n}\n","import { pageSeq, scrollSeq } from '../gestures/scroll'\nimport type { ScrollConfig, XTerminal } from '../types'\nimport { el } from '../util/dom'\nimport { conditionalFocus, isKeyboardOpen } from '../util/keyboard'\nimport { sendData } from '../util/terminal'\n\nconst LONG_PRESS_DELAY = 300\nconst REPEAT_INTERVAL = 100\nconst FADE_TIMEOUT = 2000\n\n/** Create floating scroll buttons (PgUp ▲ / PgDn ▼) */\nexport function createScrollButtons(\n\tterm: XTerminal,\n\tconfig: ScrollConfig,\n): { element: HTMLDivElement } {\n\tconst container = el('div', { id: 'wt-scroll-buttons' })\n\n\tconst upBtn = el('button', { 'aria-label': 'Page Up' }, '\\u25B2')\n\tconst downBtn = el('button', { 'aria-label': 'Page Down' }, '\\u25BC')\n\n\tcontainer.appendChild(upBtn)\n\tcontainer.appendChild(downBtn)\n\n\tfunction targetCell(): { x: number; y: number } {\n\t\t// Prefer cursor position — always in the active tmux pane\n\t\tconst active = term.buffer?.active\n\t\tif (active && typeof active.cursorX === 'number' && typeof active.cursorY === 'number') {\n\t\t\treturn {\n\t\t\t\tx: Math.max(1, active.cursorX + 1),\n\t\t\t\ty: Math.max(1, active.cursorY + 1),\n\t\t\t}\n\t\t}\n\n\t\t// Fallback: centre of terminal grid\n\t\tconst cols = typeof term.cols === 'number' && term.cols > 0 ? Math.round(term.cols) : 80\n\t\tconst rows = typeof term.rows === 'number' && term.rows > 0 ? Math.round(term.rows) : 24\n\t\treturn {\n\t\t\tx: Math.max(1, Math.floor((cols + 1) / 2)),\n\t\t\ty: Math.max(1, Math.floor((rows + 1) / 2)),\n\t\t}\n\t}\n\n\tfunction sequence(direction: 'up' | 'down'): string {\n\t\tif (config.strategy === 'keys') {\n\t\t\treturn pageSeq(direction)\n\t\t}\n\n\t\tconst { x, y } = targetCell()\n\t\treturn scrollSeq(direction, x, y)\n\t}\n\n\tfunction wireButton(button: HTMLButtonElement, direction: 'up' | 'down'): void {\n\t\tlet repeatTimer: ReturnType<typeof setInterval> | undefined\n\t\tlet delayTimer: ReturnType<typeof setTimeout> | undefined\n\n\t\tfunction send(): void {\n\t\t\tconst kbWasOpen = isKeyboardOpen()\n\t\t\tsendData(term, sequence(direction))\n\t\t\tconditionalFocus(term, kbWasOpen)\n\t\t}\n\n\t\tfunction startRepeat(): void {\n\t\t\tdelayTimer = setTimeout(() => {\n\t\t\t\trepeatTimer = setInterval(send, REPEAT_INTERVAL)\n\t\t\t}, LONG_PRESS_DELAY)\n\t\t}\n\n\t\tfunction stopRepeat(): void {\n\t\t\tif (delayTimer !== undefined) {\n\t\t\t\tclearTimeout(delayTimer)\n\t\t\t\tdelayTimer = undefined\n\t\t\t}\n\t\t\tif (repeatTimer !== undefined) {\n\t\t\t\tclearInterval(repeatTimer)\n\t\t\t\trepeatTimer = undefined\n\t\t\t}\n\t\t}\n\n\t\t// Touch events for long-press repeat\n\t\tbutton.addEventListener('touchstart', (e) => {\n\t\t\te.preventDefault()\n\t\t\tsend()\n\t\t\tstartRepeat()\n\t\t\tresetFade()\n\t\t})\n\n\t\tbutton.addEventListener('touchend', () => stopRepeat())\n\t\tbutton.addEventListener('touchcancel', () => stopRepeat())\n\n\t\t// Pointer click fallback (non-touch)\n\t\tbutton.addEventListener('click', () => {\n\t\t\tsend()\n\t\t\tresetFade()\n\t\t})\n\t}\n\n\twireButton(upBtn, 'up')\n\twireButton(downBtn, 'down')\n\n\t// Auto-fade logic\n\tlet fadeTimer: ReturnType<typeof setTimeout> | undefined\n\n\tfunction resetFade(): void {\n\t\tcontainer.classList.add('wt-active')\n\t\tif (fadeTimer !== undefined) clearTimeout(fadeTimer)\n\t\tfadeTimer = setTimeout(() => {\n\t\t\tcontainer.classList.remove('wt-active')\n\t\t}, FADE_TIMEOUT)\n\t}\n\n\treturn { element: container }\n}\n","import { createDefaultActionRegistry } from '../actions/registry'\nimport type { ActionRegistry } from '../actions/registry'\nimport type { HookRegistry } from '../hooks/registry'\nimport type { ControlButton, RemobiConfig, XTerminal } from '../types'\nimport { el } from '../util/dom'\nimport { haptic } from '../util/haptic'\nimport { conditionalFocus, isKeyboardOpen } from '../util/keyboard'\nimport { sendData } from '../util/terminal'\n\ninterface DrawerResult {\n\treadonly backdrop: HTMLDivElement\n\treadonly drawer: HTMLDivElement\n\treadonly open: () => void\n\treadonly close: () => void\n\treadonly isOpen: () => boolean\n}\n\n/** Create the command drawer with backdrop */\nexport function createDrawer(\n\tterm: XTerminal,\n\tbuttons: readonly ControlButton[],\n\tconfig: {\n\t\treadonly hooks: HookRegistry\n\t\treadonly appConfig: RemobiConfig\n\t\treadonly actions?: ActionRegistry\n\t\treadonly openComboPicker?: (options: {\n\t\t\treadonly sendText: (data: string) => Promise<void>\n\t\t\treadonly focusIfNeeded: () => void\n\t\t}) => void\n\t},\n): DrawerResult {\n\tconst actionRegistry = config.actions ?? createDefaultActionRegistry()\n\tconst hooks = config.hooks\n\tconst appConfig = config.appConfig\n\tconst backdrop = el('div', { id: 'wt-backdrop' })\n\tconst drawer = el('div', { id: 'wt-drawer' })\n\tconst handle = el('div', { id: 'wt-drawer-handle' })\n\tconst grid = el('div', { id: 'wt-drawer-grid' })\n\n\tdrawer.appendChild(handle)\n\tdrawer.appendChild(grid)\n\n\tlet drawerOpen = false\n\n\tfor (const buttonDef of buttons) {\n\t\tconst button = el('button')\n\t\tbutton.textContent = buttonDef.label\n\t\tbutton.addEventListener('click', (e: Event) => {\n\t\t\te.preventDefault()\n\t\t\tconst kbWasOpen = isKeyboardOpen()\n\t\t\thaptic()\n\t\t\tclose()\n\n\t\t\tasync function sendWithHooks(data: string): Promise<void> {\n\t\t\t\tconst before = await hooks.runBeforeSendData({\n\t\t\t\t\tterm,\n\t\t\t\t\tconfig: appConfig,\n\t\t\t\t\tsource: 'drawer',\n\t\t\t\t\tactionType: buttonDef.action.type,\n\t\t\t\t\tkbWasOpen,\n\t\t\t\t\tdata,\n\t\t\t\t})\n\t\t\t\tif (before.blocked) return\n\n\t\t\t\tsendData(term, before.data)\n\t\t\t\tawait hooks.runAfterSendData({\n\t\t\t\t\tterm,\n\t\t\t\t\tconfig: appConfig,\n\t\t\t\t\tsource: 'drawer',\n\t\t\t\t\tactionType: buttonDef.action.type,\n\t\t\t\t\tkbWasOpen,\n\t\t\t\t\tdata: before.data,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tvoid actionRegistry\n\t\t\t\t.execute(buttonDef.action, {\n\t\t\t\t\tterm,\n\t\t\t\t\tkbWasOpen,\n\t\t\t\t\tfocusIfNeeded: () => conditionalFocus(term, kbWasOpen),\n\t\t\t\t\tsendText: sendWithHooks,\n\t\t\t\t\tsendRawText: sendWithHooks,\n\t\t\t\t\topenComboPicker: config.openComboPicker,\n\t\t\t\t})\n\t\t\t\t.catch((error) => {\n\t\t\t\t\tconsole.error('remobi: drawer action execution failed', error)\n\t\t\t\t\tconditionalFocus(term, kbWasOpen)\n\t\t\t\t})\n\t\t})\n\t\tgrid.appendChild(button)\n\t}\n\n\tfunction open(): void {\n\t\tbackdrop.style.display = 'block'\n\t\tdrawer.classList.add('open')\n\t\tdrawerOpen = true\n\t}\n\n\tfunction close(): void {\n\t\tdrawer.classList.remove('open')\n\t\tbackdrop.style.display = 'none'\n\t\tdrawerOpen = false\n\t}\n\n\tfunction isOpen(): boolean {\n\t\treturn drawerOpen\n\t}\n\n\t// Backdrop dismisses drawer\n\tbackdrop.addEventListener('click', () => {\n\t\tconst kbWasOpen = isKeyboardOpen()\n\t\thaptic()\n\t\tclose()\n\t\tconditionalFocus(term, kbWasOpen)\n\t})\n\n\t// Swipe-to-dismiss on handle\n\tlet handleStartY = 0\n\n\thandle.addEventListener(\n\t\t'touchstart',\n\t\t(e: TouchEvent) => {\n\t\t\tconst touch = e.touches[0]\n\t\t\tif (touch) handleStartY = touch.clientY\n\t\t},\n\t\t{ passive: true },\n\t)\n\n\thandle.addEventListener(\n\t\t'touchmove',\n\t\t(e: TouchEvent) => {\n\t\t\tconst touch = e.touches[0]\n\t\t\tif (!touch) return\n\t\t\tconst dy = touch.clientY - handleStartY\n\t\t\tif (dy > 0) drawer.style.transform = `translateY(${dy}px)`\n\t\t},\n\t\t{ passive: true },\n\t)\n\n\thandle.addEventListener(\n\t\t'touchend',\n\t\t(e: TouchEvent) => {\n\t\t\tconst touch = e.changedTouches[0]\n\t\t\tif (!touch) return\n\t\t\tconst kbWasOpen = isKeyboardOpen()\n\t\t\tconst dy = touch.clientY - handleStartY\n\t\t\tdrawer.style.transform = ''\n\t\t\tif (dy > 60) {\n\t\t\t\tclose()\n\t\t\t\tconditionalFocus(term, kbWasOpen)\n\t\t\t}\n\t\t},\n\t\t{ passive: true },\n\t)\n\n\treturn { backdrop, drawer, open, close, isOpen }\n}\n","import type { FontConfig, XTerminal } from '../types'\nimport { resizeTerm } from '../util/terminal'\nimport type { GestureLock } from './lock'\nimport { resetLock, tryLock } from './lock'\n\n/** Calculate distance between two touch points */\nexport function touchDistance(\n\tt1: { clientX: number; clientY: number },\n\tt2: { clientX: number; clientY: number },\n): number {\n\tconst dx = t1.clientX - t2.clientX\n\tconst dy = t1.clientY - t2.clientY\n\treturn Math.sqrt(dx * dx + dy * dy)\n}\n\n/** Clamp font size to configured range */\nexport function clampFontSize(size: number, range: readonly [number, number]): number {\n\treturn Math.max(range[0], Math.min(range[1], size))\n}\n\n/** Attach pinch-to-zoom gesture to the xterm screen */\nexport function attachPinchGestures(term: XTerminal, font: FontConfig, lock: GestureLock): void {\n\tlet pinchStartDist = 0\n\tlet pinchBaseFontSize = 0\n\n\tfunction onPinchStart(e: TouchEvent): void {\n\t\tif (e.touches.length === 2) {\n\t\t\tconst t0 = e.touches[0]\n\t\t\tconst t1 = e.touches[1]\n\t\t\tif (!t0 || !t1) return\n\t\t\tpinchStartDist = touchDistance(t0, t1)\n\t\t\tpinchBaseFontSize = term.options.fontSize\n\t\t}\n\t}\n\n\tfunction onPinchMove(e: TouchEvent): void {\n\t\tif (e.touches.length !== 2) return\n\t\tif (lock.current === 'scroll') return\n\t\tif (pinchStartDist === 0) return\n\n\t\tconst t0 = e.touches[0]\n\t\tconst t1 = e.touches[1]\n\t\tif (!t0 || !t1) return\n\n\t\tconst dist = touchDistance(t0, t1)\n\t\tconst ratio = dist / pinchStartDist\n\n\t\t// Try to claim lock once ratio diverges enough\n\t\tif (lock.current === 'none' && Math.abs(ratio - 1) > 0.05) {\n\t\t\tif (!tryLock(lock, 'pinch')) return\n\t\t}\n\n\t\tif (lock.current !== 'pinch') return\n\n\t\te.preventDefault()\n\t\tconst newSize = clampFontSize(Math.round(pinchBaseFontSize * ratio), font.sizeRange)\n\t\tif (newSize !== term.options.fontSize) {\n\t\t\tterm.options.fontSize = newSize\n\t\t\tresizeTerm()\n\t\t}\n\t}\n\n\tfunction onTouchEnd(): void {\n\t\tif (lock.current === 'pinch') {\n\t\t\tresetLock(lock)\n\t\t}\n\t}\n\n\t// Wait for .xterm-screen then attach\n\tfunction attach(): void {\n\t\tconst screen = document.querySelector('.xterm-screen')\n\t\tif (!screen) {\n\t\t\tsetTimeout(attach, 200)\n\t\t\treturn\n\t\t}\n\t\t// oxlint-disable-next-line typescript/consistent-type-assertions -- DOM addEventListener types Event, not TouchEvent\n\t\tscreen.addEventListener('touchstart', (e: Event) => onPinchStart(e as TouchEvent), {\n\t\t\tpassive: true,\n\t\t})\n\t\t// oxlint-disable-next-line typescript/consistent-type-assertions -- DOM addEventListener types Event, not TouchEvent\n\t\tscreen.addEventListener('touchmove', (e: Event) => onPinchMove(e as TouchEvent), {\n\t\t\tpassive: false,\n\t\t})\n\t\tscreen.addEventListener('touchend', () => onTouchEnd(), { passive: true })\n\t}\n\n\tattach()\n}\n","import type { SwipeConfig, XTerminal } from '../types'\nimport { el } from '../util/dom'\nimport { haptic } from '../util/haptic'\nimport { sendData } from '../util/terminal'\n\n/** Result of swipe validity check — pure logic, no side effects */\nexport function isValidSwipe(\n\tdx: number,\n\tdy: number,\n\tdt: number,\n\tconfig: SwipeConfig,\n): 'left' | 'right' | null {\n\tconst absDx = Math.abs(dx)\n\tconst absDy = Math.abs(dy)\n\tif (absDx > config.threshold && dt < config.maxDuration && absDx > absDy * 2) {\n\t\treturn dx > 0 ? 'right' : 'left'\n\t}\n\treturn null\n}\n\n/** Create the swipe indicator element */\nfunction createSwipeIndicator(): { element: HTMLDivElement; show: (arrow: string) => void } {\n\tconst indicator = el('div', { id: 'wt-swipe-indicator' })\n\tlet timer = 0\n\n\tfunction show(arrow: string): void {\n\t\tindicator.textContent = arrow\n\t\tindicator.style.opacity = '1'\n\t\tclearTimeout(timer)\n\t\ttimer = window.setTimeout(() => {\n\t\t\tindicator.style.opacity = '0'\n\t\t}, 300)\n\t}\n\n\treturn { element: indicator, show }\n}\n\n/** Attach swipe gesture detection to the xterm screen */\nexport function attachSwipeGestures(\n\tterm: XTerminal,\n\tconfig: SwipeConfig,\n\tisDrawerOpen: () => boolean,\n): HTMLDivElement {\n\tconst { element: indicator, show } = createSwipeIndicator()\n\n\tlet startX = 0\n\tlet startY = 0\n\tlet startTime = 0\n\n\tfunction onTouchStart(e: TouchEvent): void {\n\t\tif (isDrawerOpen() || e.touches.length !== 1) return\n\t\tconst touch = e.touches[0]\n\t\tif (!touch) return\n\t\tstartX = touch.clientX\n\t\tstartY = touch.clientY\n\t\tstartTime = Date.now()\n\t}\n\n\tfunction onTouchEnd(e: TouchEvent): void {\n\t\tif (isDrawerOpen() || e.changedTouches.length !== 1) return\n\t\tconst touch = e.changedTouches[0]\n\t\tif (!touch) return\n\t\tconst dx = touch.clientX - startX\n\t\tconst dy = touch.clientY - startY\n\t\tconst dt = Date.now() - startTime\n\n\t\tconst direction = isValidSwipe(dx, dy, dt, config)\n\t\tif (direction === 'right') {\n\t\t\tsendData(term, config.right)\n\t\t\tshow('\\u25C0')\n\t\t\thaptic()\n\t\t} else if (direction === 'left') {\n\t\t\tsendData(term, config.left)\n\t\t\tshow('\\u25B6')\n\t\t\thaptic()\n\t\t}\n\t}\n\n\t// Wait for .xterm-screen then attach\n\tfunction attach(): void {\n\t\tconst screen = document.querySelector('.xterm-screen')\n\t\tif (!screen) {\n\t\t\tsetTimeout(attach, 200)\n\t\t\treturn\n\t\t}\n\t\t// oxlint-disable-next-line typescript/consistent-type-assertions -- DOM addEventListener types Event, not TouchEvent\n\t\tscreen.addEventListener('touchstart', (e: Event) => onTouchStart(e as TouchEvent), {\n\t\t\tpassive: true,\n\t\t})\n\t\t// oxlint-disable-next-line typescript/consistent-type-assertions -- DOM addEventListener types Event, not TouchEvent\n\t\tscreen.addEventListener('touchend', (e: Event) => onTouchEnd(e as TouchEvent), {\n\t\t\tpassive: true,\n\t\t})\n\t}\n\n\tattach()\n\treturn indicator\n}\n","import type { ButtonAction, RemobiConfig, XTerminal } from '../types'\n\nexport type SendSource = 'toolbar' | 'drawer' | 'floating-buttons' | 'mobile-init'\n\nexport interface BeforeSendDataContext {\n\treadonly term: XTerminal\n\treadonly config: RemobiConfig\n\treadonly source: SendSource\n\treadonly actionType: ButtonAction['type']\n\treadonly kbWasOpen: boolean\n\treadonly data: string\n}\n\ninterface BeforeSendDataResult {\n\treadonly data?: string\n\treadonly block?: boolean\n}\n\nexport interface AfterSendDataContext {\n\treadonly term: XTerminal\n\treadonly config: RemobiConfig\n\treadonly source: SendSource\n\treadonly actionType: ButtonAction['type']\n\treadonly kbWasOpen: boolean\n\treadonly data: string\n}\n\nexport interface OverlayInitContext {\n\treadonly term: XTerminal\n\treadonly config: RemobiConfig\n\treadonly mobile: boolean\n}\n\nexport interface ToolbarCreatedContext {\n\treadonly term: XTerminal\n\treadonly config: RemobiConfig\n\treadonly toolbar: HTMLDivElement\n}\n\nexport interface DrawerCreatedContext {\n\treadonly term: XTerminal\n\treadonly config: RemobiConfig\n\treadonly drawer: HTMLDivElement\n\treadonly backdrop: HTMLDivElement\n}\n\ninterface HookMap {\n\tbeforeSendData: (\n\t\tcontext: BeforeSendDataContext,\n\t) => BeforeSendDataResult | undefined | Promise<BeforeSendDataResult | undefined>\n\tafterSendData: (context: AfterSendDataContext) => void | Promise<void>\n\toverlayInitStart: (context: OverlayInitContext) => void | Promise<void>\n\toverlayReady: (context: OverlayInitContext) => void | Promise<void>\n\ttoolbarCreated: (context: ToolbarCreatedContext) => void | Promise<void>\n\tdrawerCreated: (context: DrawerCreatedContext) => void | Promise<void>\n}\n\ntype HookName = keyof HookMap\n\nexport interface HookRegistry {\n\ton: <K extends HookName>(name: K, hook: HookMap[K]) => { dispose(): void }\n\trunBeforeSendData: (\n\t\tcontext: BeforeSendDataContext,\n\t) => Promise<{ readonly blocked: boolean; readonly data: string }>\n\trunAfterSendData: (context: AfterSendDataContext) => Promise<void>\n\trunOverlayInitStart: (context: OverlayInitContext) => Promise<void>\n\trunOverlayReady: (context: OverlayInitContext) => Promise<void>\n\trunToolbarCreated: (context: ToolbarCreatedContext) => Promise<void>\n\trunDrawerCreated: (context: DrawerCreatedContext) => Promise<void>\n}\n\nfunction logHookError(name: HookName, error: unknown): void {\n\tconsole.error(`remobi: hook '${name}' failed`, error)\n}\n\nexport function createHookRegistry(): HookRegistry {\n\tconst hooks: { [K in HookName]: HookMap[K][] } = {\n\t\tbeforeSendData: [],\n\t\tafterSendData: [],\n\t\toverlayInitStart: [],\n\t\toverlayReady: [],\n\t\ttoolbarCreated: [],\n\t\tdrawerCreated: [],\n\t}\n\n\tfunction on<K extends HookName>(name: K, hook: HookMap[K]): { dispose(): void } {\n\t\thooks[name].push(hook)\n\t\treturn {\n\t\t\tdispose(): void {\n\t\t\t\tconst index = hooks[name].indexOf(hook)\n\t\t\t\tif (index >= 0) {\n\t\t\t\t\thooks[name].splice(index, 1)\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t}\n\n\tasync function runBeforeSendData(\n\t\tcontext: BeforeSendDataContext,\n\t): Promise<{ readonly blocked: boolean; readonly data: string }> {\n\t\tlet nextData = context.data\n\t\tfor (const hook of hooks.beforeSendData) {\n\t\t\ttry {\n\t\t\t\tconst result = await hook({ ...context, data: nextData })\n\t\t\t\tif (result?.block) {\n\t\t\t\t\treturn { blocked: true, data: nextData }\n\t\t\t\t}\n\t\t\t\tif (typeof result?.data === 'string') {\n\t\t\t\t\tnextData = result.data\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tlogHookError('beforeSendData', error)\n\t\t\t}\n\t\t}\n\t\treturn { blocked: false, data: nextData }\n\t}\n\n\tasync function runAfterSendData(context: AfterSendDataContext): Promise<void> {\n\t\tfor (const hook of hooks.afterSendData) {\n\t\t\ttry {\n\t\t\t\tawait hook(context)\n\t\t\t} catch (error) {\n\t\t\t\tlogHookError('afterSendData', error)\n\t\t\t}\n\t\t}\n\t}\n\n\tasync function runOverlayInitStart(context: OverlayInitContext): Promise<void> {\n\t\tfor (const hook of hooks.overlayInitStart) {\n\t\t\ttry {\n\t\t\t\tawait hook(context)\n\t\t\t} catch (error) {\n\t\t\t\tlogHookError('overlayInitStart', error)\n\t\t\t}\n\t\t}\n\t}\n\n\tasync function runOverlayReady(context: OverlayInitContext): Promise<void> {\n\t\tfor (const hook of hooks.overlayReady) {\n\t\t\ttry {\n\t\t\t\tawait hook(context)\n\t\t\t} catch (error) {\n\t\t\t\tlogHookError('overlayReady', error)\n\t\t\t}\n\t\t}\n\t}\n\n\tasync function runToolbarCreated(context: ToolbarCreatedContext): Promise<void> {\n\t\tfor (const hook of hooks.toolbarCreated) {\n\t\t\ttry {\n\t\t\t\tawait hook(context)\n\t\t\t} catch (error) {\n\t\t\t\tlogHookError('toolbarCreated', error)\n\t\t\t}\n\t\t}\n\t}\n\n\tasync function runDrawerCreated(context: DrawerCreatedContext): Promise<void> {\n\t\tfor (const hook of hooks.drawerCreated) {\n\t\t\ttry {\n\t\t\t\tawait hook(context)\n\t\t\t} catch (error) {\n\t\t\t\tlogHookError('drawerCreated', error)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\ton,\n\t\trunBeforeSendData,\n\t\trunAfterSendData,\n\t\trunOverlayInitStart,\n\t\trunOverlayReady,\n\t\trunToolbarCreated,\n\t\trunDrawerCreated,\n\t}\n}\n","import type { ReconnectConfig, XTerminal } from './types'\nimport { el } from './util/dom'\n\n/** Find the ttyd WebSocket from the interceptor array */\nfunction findTtydSocket(): WebSocket | undefined {\n\tconst sockets = window.__remobiSockets\n\tif (!sockets) return undefined\n\treturn sockets.find((ws) => ws.url.endsWith('/ws'))\n}\n\ninterface ReconnectOverlay {\n\treadonly element: HTMLDivElement\n\treadonly button: HTMLButtonElement\n}\n\n/** Create the reconnect overlay DOM (hidden by default) */\nfunction createOverlay(onReconnect: () => void): ReconnectOverlay {\n\tconst overlay = el('div', {\n\t\tid: 'remobi-reconnect-overlay',\n\t\tstyle: [\n\t\t\t'display:none',\n\t\t\t'position:fixed',\n\t\t\t'inset:0',\n\t\t\t'z-index:10000',\n\t\t\t'background:rgba(30,30,46,0.92)',\n\t\t\t'color:#cdd6f4',\n\t\t\t'font-family:sans-serif',\n\t\t\t'justify-content:center',\n\t\t\t'align-items:center',\n\t\t\t'flex-direction:column',\n\t\t\t'gap:16px',\n\t\t].join(';'),\n\t})\n\n\tconst message = el('div', {\n\t\tstyle: 'font-size:1.4rem;font-weight:600',\n\t})\n\tmessage.textContent = 'Connection lost'\n\n\tconst button = el('button', {\n\t\tstyle: [\n\t\t\t'padding:10px 28px',\n\t\t\t'font-size:1rem',\n\t\t\t'border:none',\n\t\t\t'border-radius:8px',\n\t\t\t'background:#cba6f7',\n\t\t\t'color:#1e1e2e',\n\t\t\t'cursor:pointer',\n\t\t\t'font-weight:600',\n\t\t].join(';'),\n\t})\n\tbutton.type = 'button'\n\tbutton.textContent = 'Reconnect'\n\tbutton.addEventListener('click', (event: Event) => {\n\t\tevent.stopPropagation()\n\t\tonReconnect()\n\t})\n\n\toverlay.addEventListener('click', () => {\n\t\tonReconnect()\n\t})\n\n\toverlay.appendChild(message)\n\toverlay.appendChild(button)\n\treturn { element: overlay, button }\n}\n\n/**\n * Set up reconnect detection and overlay.\n *\n * Watches the ttyd WebSocket for close/error events. Falls back to\n * navigator.onLine + visibilitychange if no WebSocket found.\n * Returns a dispose function that removes listeners and DOM.\n */\nexport function setupReconnect(_term: XTerminal, config: ReconnectConfig): () => void {\n\tif (!config.enabled) {\n\t\treturn () => {}\n\t}\n\n\tlet disconnected = false\n\tlet reconnectTriggered = false\n\n\tfunction triggerReconnect(): void {\n\t\tif (!disconnected || reconnectTriggered) return\n\t\treconnectTriggered = true\n\t\tlocation.reload()\n\t}\n\n\tconst { element: overlay, button } = createOverlay(triggerReconnect)\n\tdocument.body.appendChild(overlay)\n\n\tfunction onDisconnect(): void {\n\t\tif (disconnected) return\n\t\tdisconnected = true\n\t\toverlay.style.display = 'flex'\n\t\tbutton.focus()\n\t}\n\n\tfunction onOnline(): void {\n\t\tif (disconnected) {\n\t\t\ttriggerReconnect()\n\t\t}\n\t}\n\n\tfunction onVisibilityChange(): void {\n\t\tif (document.visibilityState === 'visible' && disconnected) {\n\t\t\ttriggerReconnect()\n\t\t}\n\t}\n\n\t// Try WebSocket interception first\n\tconst ws = findTtydSocket()\n\tif (ws) {\n\t\tws.addEventListener('close', onDisconnect)\n\t\tws.addEventListener('error', onDisconnect)\n\t} else {\n\t\t// Fallback: online/offline + visibilitychange heuristics\n\t\twindow.addEventListener('offline', onDisconnect)\n\t\tdocument.addEventListener('visibilitychange', onVisibilityChange)\n\t}\n\n\twindow.addEventListener('online', onOnline)\n\n\treturn () => {\n\t\tif (ws) {\n\t\t\tws.removeEventListener('close', onDisconnect)\n\t\t\tws.removeEventListener('error', onDisconnect)\n\t\t} else {\n\t\t\twindow.removeEventListener('offline', onDisconnect)\n\t\t\tdocument.removeEventListener('visibilitychange', onVisibilityChange)\n\t\t}\n\t\twindow.removeEventListener('online', onOnline)\n\t\toverlay.remove()\n\t}\n}\n","import type { TermTheme, XTerminal } from '../types'\n\n/** Apply a theme to the xterm.js terminal instance */\nexport function applyTheme(term: XTerminal, theme: TermTheme): void {\n\tterm.options.theme = { ...theme }\n}\n","import { createDefaultActionRegistry } from '../actions/registry'\nimport type { ActionRegistry } from '../actions/registry'\nimport type { HookRegistry } from '../hooks/registry'\nimport type { ControlButton, RemobiConfig, XTerminal } from '../types'\nimport { el } from '../util/dom'\nimport { haptic } from '../util/haptic'\nimport { conditionalFocus, isKeyboardOpen } from '../util/keyboard'\nimport { sendData } from '../util/terminal'\n\n/** Ctrl sticky modifier state */\ninterface CtrlState {\n\tactive: boolean\n\tdisposer: { dispose(): void } | null\n\tbuttonEl: HTMLButtonElement | null\n}\n\n/** Create the ctrl modifier state manager */\nfunction createCtrlState(): CtrlState {\n\treturn { active: false, disposer: null, buttonEl: null }\n}\n\n/** Activate ctrl sticky modifier */\nfunction activateCtrl(state: CtrlState, term: XTerminal, theme: RemobiConfig['theme']): void {\n\tif (!state.buttonEl) return\n\tstate.active = true\n\tstate.buttonEl.style.background = theme.blue\n\tstate.buttonEl.style.color = theme.background\n\n\tif (!state.disposer) {\n\t\tstate.disposer = term.onData((data: string) => {\n\t\t\tif (state.active && data.length === 1) {\n\t\t\t\tconst code = data.charCodeAt(0)\n\t\t\t\tdeactivateCtrl(state, theme)\n\t\t\t\tif ((code >= 65 && code <= 90) || (code >= 97 && code <= 122)) {\n\t\t\t\t\tsendData(term, String.fromCharCode(code & 0x1f))\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n/** Deactivate ctrl sticky modifier */\nfunction deactivateCtrl(state: CtrlState, theme: RemobiConfig['theme']): void {\n\tif (!state.buttonEl) return\n\tstate.active = false\n\tstate.buttonEl.style.background = theme.black\n\tstate.buttonEl.style.color = theme.foreground\n\n\tif (state.disposer) {\n\t\tstate.disposer.dispose()\n\t\tstate.disposer = null\n\t}\n}\n\n/** Wire up a single button's click handler based on its action type */\nfunction wireButton(\n\tbutton: HTMLButtonElement,\n\tdef: ControlButton,\n\tterm: XTerminal,\n\tctrlState: CtrlState,\n\tconfig: RemobiConfig,\n\tregistry: ActionRegistry,\n\thooks: HookRegistry,\n\topenDrawer: () => void,\n\topenComboPicker?: (options: {\n\t\treadonly sendText: (data: string) => Promise<void>\n\t\treadonly focusIfNeeded: () => void\n\t}) => void,\n): void {\n\tbutton.addEventListener('click', (e: Event) => {\n\t\te.preventDefault()\n\t\tconst kbWasOpen = isKeyboardOpen()\n\t\thaptic()\n\n\t\tasync function sendWithCtrlAware(data: string): Promise<void> {\n\t\t\tconst before = await hooks.runBeforeSendData({\n\t\t\t\tterm,\n\t\t\t\tconfig,\n\t\t\t\tsource: 'toolbar',\n\t\t\t\tactionType: def.action.type,\n\t\t\t\tkbWasOpen,\n\t\t\t\tdata,\n\t\t\t})\n\t\t\tif (before.blocked) return\n\n\t\t\tlet nextData = before.data\n\t\t\tif (ctrlState.active && ctrlState.buttonEl) {\n\t\t\t\tdeactivateCtrl(ctrlState, config.theme)\n\t\t\t\tif (nextData.length === 1) {\n\t\t\t\t\tconst code = nextData.charCodeAt(0)\n\t\t\t\t\tif ((code >= 65 && code <= 90) || (code >= 97 && code <= 122)) {\n\t\t\t\t\t\tnextData = String.fromCharCode(code & 0x1f)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsendData(term, nextData)\n\t\t\tawait hooks.runAfterSendData({\n\t\t\t\tterm,\n\t\t\t\tconfig,\n\t\t\t\tsource: 'toolbar',\n\t\t\t\tactionType: def.action.type,\n\t\t\t\tkbWasOpen,\n\t\t\t\tdata: nextData,\n\t\t\t})\n\t\t}\n\n\t\tasync function sendRaw(data: string): Promise<void> {\n\t\t\tconst before = await hooks.runBeforeSendData({\n\t\t\t\tterm,\n\t\t\t\tconfig,\n\t\t\t\tsource: 'toolbar',\n\t\t\t\tactionType: def.action.type,\n\t\t\t\tkbWasOpen,\n\t\t\t\tdata,\n\t\t\t})\n\t\t\tif (before.blocked) return\n\n\t\t\tsendData(term, before.data)\n\t\t\tawait hooks.runAfterSendData({\n\t\t\t\tterm,\n\t\t\t\tconfig,\n\t\t\t\tsource: 'toolbar',\n\t\t\t\tactionType: def.action.type,\n\t\t\t\tkbWasOpen,\n\t\t\t\tdata: before.data,\n\t\t\t})\n\t\t}\n\n\t\tvoid registry\n\t\t\t.execute(def.action, {\n\t\t\t\tterm,\n\t\t\t\tkbWasOpen,\n\t\t\t\tfocusIfNeeded: () => conditionalFocus(term, kbWasOpen),\n\t\t\t\tsendText: sendWithCtrlAware,\n\t\t\t\tsendRawText: sendRaw,\n\t\t\t\topenDrawer,\n\t\t\t\topenComboPicker,\n\t\t\t\ttoggleCtrlModifier: () => {\n\t\t\t\t\tif (ctrlState.active) {\n\t\t\t\t\t\tdeactivateCtrl(ctrlState, config.theme)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tactivateCtrl(ctrlState, term, config.theme)\n\t\t\t\t\t}\n\t\t\t\t\tconditionalFocus(term, kbWasOpen)\n\t\t\t\t},\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tconsole.error('remobi: toolbar action execution failed', error)\n\t\t\t\tconditionalFocus(term, kbWasOpen)\n\t\t\t})\n\t})\n}\n\n/** Build a row of buttons */\nfunction buildRow(\n\tbuttons: readonly ControlButton[],\n\tterm: XTerminal,\n\tctrlState: CtrlState,\n\tconfig: RemobiConfig,\n\tregistry: ActionRegistry,\n\thooks: HookRegistry,\n\topenDrawer: () => void,\n\topenComboPicker?: (options: {\n\t\treadonly sendText: (data: string) => Promise<void>\n\t\treadonly focusIfNeeded: () => void\n\t}) => void,\n): HTMLDivElement {\n\tconst row = el('div', { class: 'wt-row' })\n\n\tfor (const def of buttons) {\n\t\tconst button = el('button')\n\t\tbutton.textContent = def.label\n\t\tif (def.action.type === 'ctrl-modifier') {\n\t\t\tctrlState.buttonEl = button\n\t\t}\n\t\twireButton(button, def, term, ctrlState, config, registry, hooks, openDrawer, openComboPicker)\n\t\trow.appendChild(button)\n\t}\n\n\treturn row\n}\n\ninterface ToolbarResult {\n\treadonly element: HTMLDivElement\n\treadonly ctrlState: CtrlState\n}\n\n/** Create the two-row toolbar */\nexport function createToolbar(\n\tterm: XTerminal,\n\tconfig: RemobiConfig,\n\topenDrawer: () => void,\n\thooks: HookRegistry,\n\tactions: ActionRegistry = createDefaultActionRegistry(),\n\topenComboPicker?: (options: {\n\t\treadonly sendText: (data: string) => Promise<void>\n\t\treadonly focusIfNeeded: () => void\n\t}) => void,\n): ToolbarResult {\n\tconst toolbar = el('div', { id: 'wt-toolbar' })\n\tconst ctrlState = createCtrlState()\n\n\tconst row1 = buildRow(\n\t\tconfig.toolbar.row1,\n\t\tterm,\n\t\tctrlState,\n\t\tconfig,\n\t\tactions,\n\t\thooks,\n\t\topenDrawer,\n\t\topenComboPicker,\n\t)\n\tconst row2 = buildRow(\n\t\tconfig.toolbar.row2,\n\t\tterm,\n\t\tctrlState,\n\t\tconfig,\n\t\tactions,\n\t\thooks,\n\t\topenDrawer,\n\t\topenComboPicker,\n\t)\n\n\ttoolbar.appendChild(row1)\n\ttoolbar.appendChild(row2)\n\n\treturn { element: toolbar, ctrlState }\n}\n","import { KB_THRESHOLD } from '../util/keyboard'\n\n/**\n * Detect landscape orientation + keyboard open state.\n * In landscape with keyboard, hides row 2 and shrinks buttons via CSS class.\n */\nexport function checkLandscapeKeyboard(toolbar: HTMLDivElement): void {\n\tconst vp = window.visualViewport\n\tif (!vp) return\n\n\tconst kbOpen = window.innerHeight - vp.height > KB_THRESHOLD\n\tconst landscape = window.innerWidth > window.innerHeight\n\n\tif (kbOpen && landscape) {\n\t\ttoolbar.classList.add('wt-kb-open')\n\t} else {\n\t\ttoolbar.classList.remove('wt-kb-open')\n\t}\n}\n","import { isKeyboardOpen } from '../util/keyboard'\nimport { resizeTerm } from '../util/terminal'\nimport { checkLandscapeKeyboard } from './landscape'\n\nexport function viewportHeight(\n\tvp: Pick<VisualViewport, 'height' | 'offsetTop'> | null,\n\tfallbackHeight: number,\n\tincludeOffsetTop: boolean,\n): number {\n\tif (!vp) return fallbackHeight\n\treturn includeOffsetTop ? vp.height + vp.offsetTop : vp.height\n}\n\nexport function lockDocumentHeight(height: string): void {\n\tdocument.documentElement.style.setProperty('height', height, 'important')\n\tdocument.documentElement.style.setProperty('max-height', height, 'important')\n\tdocument.documentElement.style.setProperty('overflow', 'hidden', 'important')\n\tdocument.documentElement.style.setProperty('overscroll-behavior', 'none', 'important')\n\n\tdocument.body.style.setProperty('min-height', '0', 'important')\n\tdocument.body.style.setProperty('height', height, 'important')\n\tdocument.body.style.setProperty('max-height', height, 'important')\n\tdocument.body.style.setProperty('overflow', 'hidden', 'important')\n\tdocument.body.style.setProperty('overscroll-behavior', 'none', 'important')\n}\n\n/**\n * Manage terminal height to account for the toolbar and virtual keyboard.\n * Uses visualViewport API when available for accurate keyboard detection.\n */\nexport function initHeightManager(toolbar: HTMLDivElement): void {\n\tlet pendingResize = 0\n\n\tfunction updateHeight(): void {\n\t\tpendingResize = 0\n\t\tcheckLandscapeKeyboard(toolbar)\n\n\t\tconst vp = window.visualViewport\n\t\tconst kbOpen = isKeyboardOpen()\n\t\tconst vh = viewportHeight(vp, window.innerHeight, kbOpen)\n\t\tconst tbH = kbOpen ? 0 : toolbar.offsetHeight || 90\n\t\tconst h = `${vh - tbH}px`\n\n\t\tlockDocumentHeight(h)\n\t\tresizeTerm()\n\t}\n\n\tfunction scheduleResize(): void {\n\t\tif (!pendingResize) {\n\t\t\tpendingResize = requestAnimationFrame(updateHeight)\n\t\t}\n\t}\n\n\tif (window.visualViewport) {\n\t\twindow.visualViewport.addEventListener('resize', scheduleResize)\n\t\twindow.visualViewport.addEventListener('scroll', scheduleResize)\n\t}\n\twindow.addEventListener('resize', scheduleResize)\n\twindow.addEventListener('orientationchange', () => {\n\t\tsetTimeout(scheduleResize, 200)\n\t})\n\n\tscheduleResize()\n}\n","import { createDefaultActionRegistry } from './actions/registry'\nimport { defaultConfig } from './config'\nimport { createComboPicker } from './controls/combo-picker'\nimport { createFloatingButtons } from './controls/floating-buttons'\nimport { createFontControls } from './controls/font-size'\nimport { createHelpOverlay } from './controls/help'\nimport { createScrollButtons } from './controls/scroll-buttons'\nimport { createDrawer } from './drawer/drawer'\nimport { createGestureLock } from './gestures/lock'\nimport { attachPinchGestures } from './gestures/pinch'\nimport { attachScrollGesture } from './gestures/scroll'\nimport { attachSwipeGestures } from './gestures/swipe'\nimport { createHookRegistry } from './hooks/registry'\nimport type { HookRegistry } from './hooks/registry'\nimport { setupReconnect } from './reconnect'\nimport { applyTheme } from './theme/apply'\nimport { createToolbar } from './toolbar/toolbar'\nimport type { RemobiConfig } from './types'\nimport { resizeTerm, sendData, waitForTerm } from './util/terminal'\nimport { initHeightManager } from './viewport/height'\n\n// Re-export for package consumers\nexport { defineConfig } from './config'\nexport { createHookRegistry }\nexport type {\n\tRemobiConfig,\n\tRemobiConfigOverrides,\n\tButtonAction,\n\tButtonArrayInput,\n\tControlButton,\n\tTermTheme,\n\tFloatingButtonGroup,\n\tFloatingPosition,\n\tFloatingDirection,\n\tReconnectConfig,\n} from './types'\nexport type { HookRegistry, SendSource } from './hooks/registry'\n\n/** Detect touch device */\nfunction isMobile(): boolean {\n\treturn 'ontouchstart' in window || navigator.maxTouchPoints > 0\n}\n\n/**\n * Initialise the remobi overlay.\n * Called automatically when loaded in a browser (via the IIFE in build output).\n * Config is embedded at build time.\n */\nexport function init(\n\tconfig: RemobiConfig = defaultConfig,\n\thooks: HookRegistry = createHookRegistry(),\n): void {\n\tvoid waitForTerm()\n\t\t.then(async (term) => {\n\t\t\t// Reconnect overlay — works on both mobile and desktop\n\t\t\tconst disposeReconnect = setupReconnect(term, config.reconnect)\n\n\t\t\tconst mobile = isMobile()\n\t\t\tconst actions = createDefaultActionRegistry()\n\t\t\tlet disposed = false\n\n\t\t\tfunction dispose(): void {\n\t\t\t\tif (disposed) return\n\t\t\t\tdisposed = true\n\t\t\t\tdisposeReconnect()\n\t\t\t\twindow.removeEventListener('pagehide', onPageHide)\n\t\t\t}\n\n\t\t\tfunction onPageHide(event: PageTransitionEvent): void {\n\t\t\t\tif (event.persisted) return\n\t\t\t\tdispose()\n\t\t\t}\n\n\t\t\twindow.addEventListener('beforeunload', dispose, { once: true })\n\t\t\twindow.addEventListener('pagehide', onPageHide)\n\n\t\t\ttry {\n\t\t\t\tawait hooks.runOverlayInitStart({ term, config, mobile })\n\n\t\t\t\t// Resize after fonts load — catch silently; font failure is non-critical\n\t\t\t\tdocument.fonts.ready.then(() => resizeTerm()).catch(() => {})\n\n\t\t\t\tdocument.title = `${config.name} · ${location.hostname.replace(/\\..*/, '')}`\n\n\t\t\t\tif (!mobile) {\n\t\t\t\t\tawait hooks.runOverlayReady({ term, config, mobile })\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Apply theme and font\n\t\t\t\tapplyTheme(term, config.theme)\n\t\t\t\tterm.options.fontSize = config.font.mobileSizeDefault\n\t\t\t\tterm.options.fontFamily = config.font.family\n\t\t\t\tresizeTerm()\n\n\t\t\t\t// CSS is injected as a <style> tag by the build script (build.ts)\n\n\t\t\t\tconst comboPicker = createComboPicker()\n\t\t\t\tdocument.body.appendChild(comboPicker.element)\n\n\t\t\t\t// Create drawer (needed by toolbar for toggle)\n\t\t\t\tconst drawer = createDrawer(term, config.drawer.buttons, {\n\t\t\t\t\thooks,\n\t\t\t\t\tappConfig: config,\n\t\t\t\t\tactions,\n\t\t\t\t\topenComboPicker: comboPicker.open,\n\t\t\t\t})\n\t\t\t\tdocument.body.appendChild(drawer.backdrop)\n\t\t\t\tdocument.body.appendChild(drawer.drawer)\n\t\t\t\tawait hooks.runDrawerCreated({\n\t\t\t\t\tterm,\n\t\t\t\t\tconfig,\n\t\t\t\t\tdrawer: drawer.drawer,\n\t\t\t\t\tbackdrop: drawer.backdrop,\n\t\t\t\t})\n\n\t\t\t\t// Create toolbar\n\t\t\t\tconst { element: toolbar } = createToolbar(\n\t\t\t\t\tterm,\n\t\t\t\t\tconfig,\n\t\t\t\t\tdrawer.open,\n\t\t\t\t\thooks,\n\t\t\t\t\tactions,\n\t\t\t\t\tcomboPicker.open,\n\t\t\t\t)\n\t\t\t\tdocument.body.appendChild(toolbar)\n\t\t\t\tawait hooks.runToolbarCreated({ term, config, toolbar })\n\n\t\t\t\t// Font controls + help\n\t\t\t\tconst { element: fontControls, helpButton } = createFontControls(term, config.font)\n\t\t\t\tdocument.body.appendChild(fontControls)\n\n\t\t\t\t// Floating button groups (always visible on touch devices)\n\t\t\t\tif (config.floatingButtons.length > 0) {\n\t\t\t\t\tconst { elements: floatingEls } = createFloatingButtons(\n\t\t\t\t\t\tterm,\n\t\t\t\t\t\tconfig.floatingButtons,\n\t\t\t\t\t\tconfig,\n\t\t\t\t\t\thooks,\n\t\t\t\t\t\tactions,\n\t\t\t\t\t\tdrawer.open,\n\t\t\t\t\t\tcomboPicker.open,\n\t\t\t\t\t)\n\t\t\t\t\tfor (const floatingEl of floatingEls) {\n\t\t\t\t\t\tdocument.body.appendChild(floatingEl)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Scroll buttons\n\t\t\t\tconst { element: scrollButtons } = createScrollButtons(term, config.gestures.scroll)\n\t\t\t\tdocument.body.appendChild(scrollButtons)\n\n\t\t\t\t// Gestures\n\t\t\t\tconst gestureLock = createGestureLock()\n\t\t\t\tif (config.gestures.swipe.enabled) {\n\t\t\t\t\tconst indicator = attachSwipeGestures(term, config.gestures.swipe, drawer.isOpen)\n\t\t\t\t\tdocument.body.appendChild(indicator)\n\t\t\t\t}\n\t\t\t\tif (config.gestures.pinch.enabled) {\n\t\t\t\t\tattachPinchGestures(term, config.font, gestureLock)\n\t\t\t\t}\n\t\t\t\tif (config.gestures.scroll.enabled) {\n\t\t\t\t\tattachScrollGesture(term, config.gestures.scroll, gestureLock, drawer.isOpen)\n\t\t\t\t}\n\n\t\t\t\t// Height management\n\t\t\t\tinitHeightManager(toolbar)\n\n\t\t\t\t// Mobile init data: send once on load if viewport is narrow enough.\n\t\t\t\t// Already inside isMobile() guard (touch detection). widthThreshold adds a\n\t\t\t\t// second filter — a wide-viewport touch device (e.g. landscape tablet) may\n\t\t\t\t// not want mobile init behaviour.\n\t\t\t\tif (config.mobile.initData !== null && window.innerWidth < config.mobile.widthThreshold) {\n\t\t\t\t\tconst data = config.mobile.initData\n\t\t\t\t\tconst before = await hooks.runBeforeSendData({\n\t\t\t\t\t\tterm,\n\t\t\t\t\t\tconfig,\n\t\t\t\t\t\tsource: 'mobile-init',\n\t\t\t\t\t\tactionType: 'send',\n\t\t\t\t\t\tkbWasOpen: false,\n\t\t\t\t\t\tdata,\n\t\t\t\t\t})\n\t\t\t\t\tif (!before.blocked) {\n\t\t\t\t\t\tsendData(term, before.data)\n\t\t\t\t\t\tawait hooks.runAfterSendData({\n\t\t\t\t\t\t\tterm,\n\t\t\t\t\t\t\tconfig,\n\t\t\t\t\t\t\tsource: 'mobile-init',\n\t\t\t\t\t\t\tactionType: 'send',\n\t\t\t\t\t\t\tkbWasOpen: false,\n\t\t\t\t\t\t\tdata: before.data,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Help overlay should never break core controls.\n\t\t\t\ttry {\n\t\t\t\t\tconst { element: helpOverlay } = createHelpOverlay(term, helpButton, config)\n\t\t\t\t\tdocument.body.appendChild(helpOverlay)\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error('remobi: failed to initialise help overlay', error)\n\t\t\t\t}\n\n\t\t\t\tawait hooks.runOverlayReady({ term, config, mobile })\n\t\t\t} catch (error) {\n\t\t\t\tdispose()\n\t\t\t\tthrow error\n\t\t\t}\n\t\t})\n\t\t.catch((error) => {\n\t\t\tconsole.error('remobi: failed to initialise overlay', error)\n\t\t})\n}\n"],"mappings":"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"}
@@ -0,0 +1,183 @@
1
+ //#region src/types.d.ts
2
+ /** Action types for control buttons — discriminated union, no boolean flags */
3
+ type ButtonAction = {
4
+ readonly type: 'send';
5
+ readonly data: string;
6
+ readonly keyLabel?: string;
7
+ } | {
8
+ readonly type: 'ctrl-modifier';
9
+ } | {
10
+ readonly type: 'paste';
11
+ } | {
12
+ readonly type: 'combo-picker';
13
+ } | {
14
+ readonly type: 'drawer-toggle';
15
+ };
16
+ /** A generic control button definition used by toolbar and drawer */
17
+ interface ControlButton {
18
+ readonly id: string;
19
+ readonly label: string;
20
+ readonly description: string;
21
+ readonly action: ButtonAction;
22
+ }
23
+ /** xterm.js theme colours */
24
+ interface TermTheme {
25
+ readonly background: string;
26
+ readonly foreground: string;
27
+ readonly cursor: string;
28
+ readonly cursorAccent: string;
29
+ readonly selectionBackground: string;
30
+ readonly black: string;
31
+ readonly red: string;
32
+ readonly green: string;
33
+ readonly yellow: string;
34
+ readonly blue: string;
35
+ readonly magenta: string;
36
+ readonly cyan: string;
37
+ readonly white: string;
38
+ readonly brightBlack: string;
39
+ readonly brightRed: string;
40
+ readonly brightGreen: string;
41
+ readonly brightYellow: string;
42
+ readonly brightBlue: string;
43
+ readonly brightMagenta: string;
44
+ readonly brightCyan: string;
45
+ readonly brightWhite: string;
46
+ }
47
+ /** Font configuration */
48
+ interface FontConfig {
49
+ readonly family: string;
50
+ readonly cdnUrl: string;
51
+ readonly mobileSizeDefault: number;
52
+ readonly sizeRange: readonly [min: number, max: number];
53
+ }
54
+ /** Swipe gesture configuration */
55
+ interface SwipeConfig {
56
+ readonly enabled: boolean;
57
+ readonly threshold: number;
58
+ readonly maxDuration: number;
59
+ readonly left: string;
60
+ readonly right: string;
61
+ readonly leftLabel: string;
62
+ readonly rightLabel: string;
63
+ }
64
+ /** Pinch gesture configuration */
65
+ interface PinchConfig {
66
+ readonly enabled: boolean;
67
+ }
68
+ /** Scroll gesture configuration */
69
+ type ScrollStrategy = 'keys' | 'wheel';
70
+ /** Scroll gesture configuration */
71
+ interface ScrollConfig {
72
+ readonly enabled: boolean;
73
+ readonly sensitivity: number;
74
+ readonly strategy: ScrollStrategy;
75
+ readonly wheelIntervalMs: number;
76
+ }
77
+ /** Gesture configuration */
78
+ interface GestureConfig {
79
+ readonly swipe: SwipeConfig;
80
+ readonly pinch: PinchConfig;
81
+ readonly scroll: ScrollConfig;
82
+ }
83
+ /** Mobile-specific behaviour configuration */
84
+ interface MobileConfig {
85
+ /** Data to send to the terminal on mobile init, null = disabled */
86
+ readonly initData: string | null;
87
+ /** Viewport width (px) below which mobile init behaviour triggers */
88
+ readonly widthThreshold: number;
89
+ }
90
+ /** Viewport position for a floating button group */
91
+ type FloatingPosition = 'top-left' | 'top-right' | 'top-centre' | 'bottom-left' | 'bottom-right' | 'bottom-centre' | 'centre-left' | 'centre-right';
92
+ /** Layout direction for a floating button group */
93
+ type FloatingDirection = 'row' | 'column';
94
+ /** A positioned group of floating buttons */
95
+ interface FloatingButtonGroup {
96
+ readonly position: FloatingPosition;
97
+ readonly direction?: FloatingDirection;
98
+ readonly buttons: readonly ControlButton[];
99
+ }
100
+ /** Reconnect overlay configuration */
101
+ interface ReconnectConfig {
102
+ readonly enabled: boolean;
103
+ }
104
+ /** PWA (Progressive Web App) configuration */
105
+ interface PwaConfig {
106
+ readonly enabled: boolean;
107
+ readonly shortName?: string;
108
+ readonly themeColor: string;
109
+ }
110
+ /** Full remobi configuration */
111
+ interface RemobiConfig {
112
+ readonly name: string;
113
+ readonly theme: TermTheme;
114
+ readonly font: FontConfig;
115
+ readonly toolbar: {
116
+ readonly row1: readonly ControlButton[];
117
+ readonly row2: readonly ControlButton[];
118
+ };
119
+ readonly drawer: {
120
+ readonly buttons: readonly ControlButton[];
121
+ };
122
+ readonly gestures: GestureConfig;
123
+ readonly mobile: MobileConfig;
124
+ readonly floatingButtons: readonly FloatingButtonGroup[];
125
+ readonly pwa: PwaConfig;
126
+ readonly reconnect: ReconnectConfig;
127
+ }
128
+ /** Deep partial — allows overriding any nested subset of config */
129
+ type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P] };
130
+ /**
131
+ * Input form for a button array in config overrides.
132
+ * - Array: replace defaults entirely
133
+ * - Function: receive defaults, return new array (filter, reorder, append, etc.)
134
+ */
135
+ type ButtonArrayInput<T extends {
136
+ readonly id: string;
137
+ }> = readonly T[] | ((defaults: readonly T[]) => readonly T[]);
138
+ /** Config overrides shape that supports ButtonArrayInput for button arrays */
139
+ type RemobiConfigOverrides = Omit<DeepPartial<RemobiConfig>, 'toolbar' | 'drawer' | 'floatingButtons'> & {
140
+ readonly toolbar?: {
141
+ readonly row1?: ButtonArrayInput<ControlButton>;
142
+ readonly row2?: ButtonArrayInput<ControlButton>;
143
+ };
144
+ readonly drawer?: {
145
+ readonly buttons?: ButtonArrayInput<ControlButton>;
146
+ };
147
+ readonly floatingButtons?: readonly FloatingButtonGroup[];
148
+ };
149
+ /**
150
+ * Minimal xterm.js Terminal interface — only what remobi needs.
151
+ * Avoids importing the full xterm package.
152
+ */
153
+ interface XTerminal {
154
+ cols?: number;
155
+ rows?: number;
156
+ buffer?: {
157
+ active: {
158
+ cursorX: number;
159
+ cursorY: number;
160
+ };
161
+ };
162
+ options: {
163
+ fontSize: number;
164
+ theme?: Record<string, string>;
165
+ fontFamily?: string;
166
+ };
167
+ input(data: string, wasUserInput: boolean): void;
168
+ focus(): void;
169
+ onData(handler: (data: string) => void): {
170
+ dispose(): void;
171
+ };
172
+ }
173
+ /** ttyd sets window.term — typed globally to avoid unsafe casts */
174
+ declare global {
175
+ interface Window {
176
+ term?: XTerminal;
177
+ /** WebSocket instances captured by the reconnect interceptor script */
178
+ __remobiSockets?: WebSocket[];
179
+ }
180
+ } //# sourceMappingURL=types.d.ts.map
181
+ //#endregion
182
+ export { ButtonAction, ButtonArrayInput, ControlButton, DeepPartial, FloatingButtonGroup, FloatingDirection, FloatingPosition, FontConfig, GestureConfig, MobileConfig, PinchConfig, PwaConfig, ReconnectConfig, RemobiConfig, RemobiConfigOverrides, ScrollConfig, ScrollStrategy, SwipeConfig, TermTheme, XTerminal };
183
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../src/types.ts"],"mappings":";;KACY,YAAA;EAAA,SACE,IAAA;EAAA,SAAuB,IAAA;EAAA,SAAuB,QAAA;AAAA;EAAA,SAC9C,IAAA;AAAA;EAAA,SACA,IAAA;AAAA;EAAA,SACA,IAAA;AAAA;EAAA,SACA,IAAA;AAAA;AAGd;AAAA,UAAiB,aAAA;EAAA,SACP,EAAA;EAAA,SACA,KAAA;EAAA,SACA,WAAA;EAAA,SACA,MAAA,EAAQ,YAAA;AAAA;;UAID,SAAA;EAAA,SACP,UAAA;EAAA,SACA,UAAA;EAAA,SACA,MAAA;EAAA,SACA,YAAA;EAAA,SACA,mBAAA;EAAA,SACA,KAAA;EAAA,SACA,GAAA;EAAA,SACA,KAAA;EAAA,SACA,MAAA;EAAA,SACA,IAAA;EAAA,SACA,OAAA;EAAA,SACA,IAAA;EAAA,SACA,KAAA;EAAA,SACA,WAAA;EAAA,SACA,SAAA;EAAA,SACA,WAAA;EAAA,SACA,YAAA;EAAA,SACA,UAAA;EAAA,SACA,aAAA;EAAA,SACA,UAAA;EAAA,SACA,WAAA;AAAA;;UAIO,UAAA;EAAA,SACP,MAAA;EAAA,SACA,MAAA;EAAA,SACA,iBAAA;EAAA,SACA,SAAA,YAAqB,GAAA,UAAa,GAAA;AAAA;AAJ5C;AAAA,UAQiB,WAAA;EAAA,SACP,OAAA;EAAA,SACA,SAAA;EAAA,SACA,WAAA;EAAA,SACA,IAAA;EAAA,SACA,KAAA;EAAA,SACA,SAAA;EAAA,SACA,UAAA;AAAA;;UAIO,WAAA;EAAA,SACP,OAAA;AAAA;;KAIE,cAAA;;UAGK,YAAA;EAAA,SACP,OAAA;EAAA,SACA,WAAA;EAAA,SACA,QAAA,EAAU,cAAA;EAAA,SACV,eAAA;AAAA;;UAIO,aAAA;EAAA,SACP,KAAA,EAAO,WAAA;EAAA,SACP,KAAA,EAAO,WAAA;EAAA,SACP,MAAA,EAAQ,YAAA;AAAA;;UAID,YAAA;EAlBS;EAAA,SAoBhB,QAAA;EApBgB;EAAA,SAsBhB,cAAA;AAAA;;KAIE,gBAAA;;KAWA,iBAAA;;UAGK,mBAAA;EAAA,SACP,QAAA,EAAU,gBAAA;EAAA,SACV,SAAA,GAAY,iBAAA;EAAA,SACZ,OAAA,WAAkB,aAAA;AAAA;AAhC5B;AAAA,UAoCiB,eAAA;EAAA,SACP,OAAA;AAAA;;UAIO,SAAA;EAAA,SACP,OAAA;EAAA,SACA,SAAA;EAAA,SACA,UAAA;AAAA;;UAIO,YAAA;EAAA,SACP,IAAA;EAAA,SACA,KAAA,EAAO,SAAA;EAAA,SACP,IAAA,EAAM,UAAA;EAAA,SACN,OAAA;IAAA,SACC,IAAA,WAAe,aAAA;IAAA,SACf,IAAA,WAAe,aAAA;EAAA;EAAA,SAEhB,MAAA;IAAA,SACC,OAAA,WAAkB,aAAA;EAAA;EAAA,SAEnB,QAAA,EAAU,aAAA;EAAA,SACV,MAAA,EAAQ,YAAA;EAAA,SACR,eAAA,WAA0B,mBAAA;EAAA,SAC1B,GAAA,EAAK,SAAA;EAAA,SACL,SAAA,EAAW,eAAA;AAAA;;KAIT,WAAA,oBACC,CAAA,IAAK,CAAA,CAAE,CAAA,mBAAoB,WAAA,CAAY,CAAA,CAAE,CAAA,KAAM,CAAA,CAAE,CAAA;;;;;;KAQlD,gBAAA;EAAA,SAAsC,EAAA;AAAA,cACtC,CAAA,OACP,QAAA,WAAmB,CAAA,gBAAiB,CAAA;;KAG7B,qBAAA,GAAwB,IAAA,CACnC,WAAA,CAAY,YAAA;EAAA,SAGH,OAAA;IAAA,SACC,IAAA,GAAO,gBAAA,CAAiB,aAAA;IAAA,SACxB,IAAA,GAAO,gBAAA,CAAiB,aAAA;EAAA;EAAA,SAEzB,MAAA;IAAA,SACC,OAAA,GAAU,gBAAA,CAAiB,aAAA;EAAA;EAAA,SAE5B,eAAA,YAA2B,mBAAA;AAAA;;AAnDrC;;;UA0DiB,SAAA;EAChB,IAAA;EACA,IAAA;EACA,MAAA;IACC,MAAA;MACC,OAAA;MACA,OAAA;IAAA;EAAA;EAGF,OAAA;IACC,QAAA;IACA,KAAA,GAAQ,MAAA;IACR,UAAA;EAAA;EAED,KAAA,CAAM,IAAA,UAAc,YAAA;EACpB,KAAA;EACA,MAAA,CAAO,OAAA,GAAU,IAAA;IAA0B,OAAA;EAAA;AAAA;;QAIpC,MAAA;EAAA,UACG,MAAA;IACT,IAAA,GAAO,SAAA;IAtEC;IAwER,eAAA,GAAkB,SAAA;EAAA;AAAA"}
@@ -0,0 +1 @@
1
+ export { };
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "remobi",
3
+ "version": "0.1.0",
4
+ "description": "Monitor and control your coding agents from your phone. Touch controls for tmux over the web.",
5
+ "type": "module",
6
+ "bin": {
7
+ "remobi": "./dist/cli.mjs"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/src/index.d.mts",
12
+ "default": "./dist/src/index.mjs"
13
+ },
14
+ "./config": {
15
+ "types": "./dist/src/config.d.mts",
16
+ "default": "./dist/src/config.mjs"
17
+ },
18
+ "./types": {
19
+ "types": "./dist/src/types.d.mts"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist/",
24
+ "styles/",
25
+ "src/pwa/icons/",
26
+ "README.md",
27
+ "CHANGELOG.md",
28
+ "LICENSE"
29
+ ],
30
+ "engines": {
31
+ "node": ">=22.0.0"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/connorads/remobi.git"
36
+ },
37
+ "homepage": "https://github.com/connorads/remobi",
38
+ "bugs": "https://github.com/connorads/remobi/issues",
39
+ "devDependencies": {
40
+ "@biomejs/biome": "^1.9.0",
41
+ "esbuild": "^0.25.0",
42
+ "@happy-dom/global-registrator": "^20.6.1",
43
+ "@types/node": "^22.0.0",
44
+ "@types/ws": "^8.18.0",
45
+ "knip": "^5.85.0",
46
+ "oxlint": "^1.50.0",
47
+ "publint": "^0.3.17",
48
+ "tsdown": "^0.20.0",
49
+ "tsx": "^4.19.0",
50
+ "typescript": "^5.9.3",
51
+ "vitest": "^3.0.0"
52
+ },
53
+ "license": "MIT",
54
+ "author": "Connor",
55
+ "keywords": [
56
+ "ttyd",
57
+ "tmux",
58
+ "terminal",
59
+ "mobile",
60
+ "overlay",
61
+ "touch",
62
+ "node",
63
+ "agents",
64
+ "claude",
65
+ "remote",
66
+ "pwa"
67
+ ],
68
+ "dependencies": {
69
+ "@hono/node-server": "^1.14.0",
70
+ "@hono/node-ws": "^1.1.0",
71
+ "hono": "^4.7.0",
72
+ "valibot": "^1.2.0",
73
+ "ws": "^8.18.0"
74
+ },
75
+ "scripts": {
76
+ "build:overlay": "tsx scripts/build-overlay.ts",
77
+ "build:dist": "tsdown && pnpm run build:overlay",
78
+ "build": "tsx cli.ts build",
79
+ "check": "biome check .",
80
+ "check:fix": "biome check --fix .",
81
+ "lint:knip": "knip",
82
+ "lint:ox": "oxlint --import-plugin --promise-plugin",
83
+ "lint:publint": "publint",
84
+ "lint:typos": "typos",
85
+ "test": "vitest run",
86
+ "test:coverage": "vitest run --coverage"
87
+ }
88
+ }
Binary file
Binary file
Binary file