' =================================================================== ' File: paste-submit.puml ' Purpose: Single source of truth for module-level activity + per-function sequences. ' Module: paste-submit.js — Find composer, paste text robustly, and optionally submit. ' Edit rules: Follow the legend at bottom; preserve VIEW/METHOD anchors for automation. ' =================================================================== ' Neutral defaults — typography/layout only (keeps partition colors intact) skinparam Shadowing false skinparam SequenceMessageAlign center skinparam SequenceLifeLineBorderColor #666666 skinparam SequenceLifeLineBorderThickness 1 ' ==== VIEW: Branch Flow (paste-submit.js) ================================== @startuml title paste-submit.js — Branch Flow (full module) start :paste-submit; fork ' -------- findComposer() -------- partition "findComposer()" #E7FAE3 { :findComposer; :iterate selectors in priority order;\nskip hidden/disabled/offsetParent null (unless fixed); :return first visible editor or null; kill } fork again ' -------- findSendButton(scopeEl) -------- partition "findSendButton(scopeEl)" #FFF6D1 { :findSendButton; :scope = closest(form/composer/main/body);\nquery candidate buttons; filter disabled/hidden; :return button or null; kill } fork again ' -------- pressEnter(el) -------- partition "pressEnter(el)" #FFE1DB { :pressEnter; :dispatch keydown/keypress/keyup Enter;\nreturn success boolean; kill } fork again ' -------- waitReady(timeoutMs) -------- partition "waitReady(timeoutMs)" #DCF9EE { :waitReady; :loop until timeout: findComposer();\nensure not busy and editor empty; :return true/false; kill } fork again ' -------- pasteInto(el, text) -------- partition "pasteInto(el, text)" #FFE6F0 { :pasteInto; :payload = maybe add trailing newline (cfg.ui.appendTrailingNewline); :try ClipboardEvent path -> return true; :if ProseMirror -> set text + input -> true; :if contentEditable -> selection insert + input -> true; :if textarea/input -> value + input -> true; :fallback GM_setClipboard + alert -> true; :else return false; kill } fork again ' -------- submitToComposer(text) -------- partition "submitToComposer(text)" #E6F3FF { :submitToComposer; :auto = cfg.ui.autoSubmit;\nok = waitReady(...);\nif !ok -> warn + false; :el = findComposer(); pasteInto if text;\npostPasteDelay; if !auto -> true; :btn = findSendButton(el)? click : pressEnter(el); :return boolean; kill } end fork @enduml ' ==== METHOD: findComposer() =============================================== @startuml title paste-submit:findComposer(): \n Locate a visible, interactable chat editor element participant "Caller" as CL participant "findComposer()" as FC participant "DOM" as DOM activate CL CL -> FC : initial request activate FC FC -> FC : for s in selectors (priority list) FC -> DOM : querySelector(s) DOM --> FC : el | null alt el found FC -> FC : st = getComputedStyle(el)\nif hidden/visibility:hidden -> continue\nif offsetParent==null && position!='fixed' -> continue alt visible + interactable FC --> CL : el deactivate FC deactivate CL return else skip end end FC --> CL : null deactivate FC deactivate CL @enduml ' ==== METHOD: findSendButton(scopeEl) ====================================== @startuml title paste-submit:findSendButton(scopeEl): \n Find a nearby, visible Send/Submit button participant "Caller" as CL participant "findSendButton(scopeEl)" as FSB participant "DOM" as DOM activate CL CL -> FSB : scopeEl activate FSB FSB -> DOM : scope = scopeEl.closest('form,[data-testid="composer"],main,body') || document DOM --> FSB : scope FSB -> FSB : for s in candidate selectors FSB -> DOM : scope.querySelector(s) || document.querySelector(s) DOM --> FSB : b | null alt b found FSB -> FSB : st = getComputedStyle(b); disabled/hidden/offsetParent? alt clickable FSB --> CL : b deactivate FSB deactivate CL return else continue end end FSB --> CL : null deactivate FSB deactivate CL @enduml ' ==== METHOD: pressEnter(el) =============================================== @startuml title paste-submit:pressEnter(el): \n Simulate Enter keystrokes against the editor participant "Caller" as CL participant "pressEnter(el)" as PE participant "DOM" as DOM activate CL CL -> PE : el activate PE loop for t in ['keydown','keypress','keyup'] PE -> DOM : dispatch KeyboardEvent(t,{Enter, bubbles:true, cancelable:true}) DOM --> PE : ok (boolean) alt not ok PE --> CL : false deactivate PE deactivate CL return end end PE --> CL : true deactivate PE deactivate CL @enduml ' ==== METHOD: waitReady(timeoutMs) ========================================= @startuml title paste-submit:waitReady(timeoutMs): \n Ensure composer exists, is idle, and empty participant "Caller" as CL participant "waitReady(timeoutMs)" as WR participant "findComposer()" as FC participant "DOM" as DOM participant "Timer" as TMR activate CL CL -> WR : timeoutMs activate WR WR -> WR : start = now loop until now - start < timeoutMs WR -> FC : findComposer() FC --> WR : el | null alt el present WR -> WR : current = (el.textContent || el.value || '').trim() WR -> DOM : search closest(...).querySelector('[aria-busy="true"],[data-state="loading"],.typing-indicator') DOM --> WR : busyEl | null alt !busyEl && current.length == 0 WR --> CL : true deactivate WR deactivate CL return end end WR -> TMR : setTimeout(200ms) TMR --> WR : tick end WR --> CL : false deactivate WR deactivate CL @enduml ' ==== METHOD: pasteInto(el, text) ========================================== @startuml title paste-submit:pasteInto(el, text): \n Paste via multiple strategies from cleanest to fallback participant "Caller" as CL participant "pasteInto(el, text)" as PI participant "Config" as CFG participant "DOM" as DOM participant "GM_setClipboard" as CLP activate CL CL -> PI : el, text activate PI ' payload prep PI -> CFG : get('ui.appendTrailingNewline') CFG --> PI : boolean PI -> PI : payload = (append?\n text.endsWith('\\n')?text:text+'\\n' : text) ' Strategy 1: ClipboardEvent path PI -> PI : try { dt = new DataTransfer(); dt.setData('text/plain', payload) } PI -> DOM : dispatch ClipboardEvent('paste', {clipboardData:dt,bubbles:true,cancelable:true}) DOM --> PI : dispatched/blocked alt event accepted and not defaultPrevented PI --> CL : true deactivate PI deactivate CL return end ' Strategy 2: ProseMirror PI -> PI : if el.classList.contains('ProseMirror')\n el.innerHTML=''; append text node; dispatch 'input' alt ProseMirror handled PI --> CL : true deactivate PI deactivate CL return end ' Strategy 3: contentEditable selection PI -> PI : if el.isContentEditable or contenteditable='true' PI -> DOM : ensure selection range at end DOM --> PI : range | null alt range present PI -> DOM : range.deleteContents(); insertTextNode(payload); move caret; dispatch 'input' DOM --> PI : ok PI --> CL : true deactivate PI deactivate CL return end ' Strategy 4: textarea/input PI -> PI : if tagName in {TEXTAREA, INPUT}\n el.value=payload; dispatch 'input' alt text control handled PI --> CL : true deactivate PI deactivate CL return end ' Strategy 5: clipboard fallback PI -> CLP : GM_setClipboard(payload, {type:'text',mimetype:'text/plain'}) CLP --> PI : ok | error alt ok PI -> DOM : alert('Content copied to clipboard. Press Ctrl/Cmd+V to paste.') DOM --> PI : shown PI --> CL : true else failed PI --> CL : false end deactivate PI deactivate CL @enduml ' ==== METHOD: submitToComposer(text) ======================================= @startuml title paste-submit:submitToComposer(text): \n Wait until ready, paste text, and optionally auto-submit participant "Caller" as CL participant "submitToComposer(text)" as SUB participant "Config" as CFG participant "Logger" as LOG participant "waitReady(timeoutMs)" as WR participant "findComposer()" as FC participant "pasteInto(el, text)" as PI participant "findSendButton(scopeEl)" as FSB participant "pressEnter(el)" as PE participant "Timer" as TMR activate CL CL -> SUB : text activate SUB ' readiness SUB -> CFG : get('ui.autoSubmit') CFG --> SUB : auto (bool) SUB -> CFG : get('execution.settleCheckMs') || 1200 CFG --> SUB : settleMs SUB -> WR : waitReady(settleMs) WR --> SUB : ok (bool) alt !ok SUB -> LOG : warn("Composer not ready") SUB --> CL : false deactivate SUB deactivate CL return end ' find composer + paste SUB -> FC : findComposer() FC --> SUB : el | null alt el is null SUB -> LOG : warn("Composer not found") SUB --> CL : false deactivate SUB deactivate CL return end alt text provided SUB -> PI : pasteInto(el, text) PI --> SUB : pasted (bool) alt !pasted SUB -> LOG : warn("Paste failed") SUB --> CL : false deactivate SUB deactivate CL return end end ' post-paste delay SUB -> CFG : get('ui.postPasteDelayMs') || 600 CFG --> SUB : postMs SUB -> TMR : setTimeout(postMs) TMR --> SUB : wake alt auto == false SUB --> CL : true deactivate SUB deactivate CL return end ' submit by button or enter SUB -> FSB : findSendButton(el) FSB --> SUB : btn | null alt btn found SUB -> SUB : btn.click() SUB --> CL : true else no button SUB -> PE : pressEnter(el) PE --> SUB : ok (bool) SUB --> CL : ok end deactivate SUB deactivate CL @enduml ' ==== LEGEND =============================================================== @startuml legend bottom == paste-submit UML Style Guide (for future edits) == • Scope: One .puml per module. Keep two views: (1) Activity "Branch Flow" for the whole module (partitions + soft colors), (2) Per-function Sequence diagrams for each exported or significant internal method. • Sequence conventions: 1) First participant is the external caller (use "Caller" or a concrete origin). 2) Do NOT add a module lifeline; the module name appears in the title only. 3) Include every directly-called method or subsystem as a participant (e.g., "findComposer()", "pasteInto()", "findSendButton()", "submitToComposer()", "pressEnter()", "waitReady()", "Config", "Logger", "DOM", "Timer", "GM_setClipboard"). 4) Prefer simple messages; Use --> for returns; -> for calls. 5) Use activate/deactivate as you see fit for clarity (no strict rule). 6) Use alt blocks only when branches meaningfully change the message flow. • Activity view conventions: A) Start with module node then fork partitions for each function/method. B) One partition per function; soft background color; terminate branches with 'kill'. C) Keep wording aligned with code (e.g., selector heuristics, delay knobs, config flags). • Color palette (soft pastels) • Use --> for returns; -> for calls. • Participants use quoted method names for internals (e.g., "pasteInto()"), and plain nouns for systems ("DOM", "GM_setClipboard", "Timer"). • Keep this legend at the end of the file to standardize edits. endlegend @enduml