566 lines
16 KiB
Plaintext
566 lines
16 KiB
Plaintext
' ===================================================================
|
|
' File: debug-panel.puml
|
|
' Purpose: Single source of truth for module-level activity + per-method sequences.
|
|
' Module: debug-panel.js — Draggable in-page panel: logs tail, tools/settings, queue, pause/stop.
|
|
' 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 (debug-panel.js) ===================================
|
|
@startuml
|
|
title debug-panel.js — Branch Flow (full module)
|
|
|
|
start
|
|
:debug-panel;
|
|
|
|
fork
|
|
' -------- DebugPanel.constructor() --------
|
|
partition "DebugPanel.constructor()" #E7FAE3 {
|
|
:constructor;
|
|
:panel/bodyLogs/bodyTools = null;\ncollapsed=false; drag={active:false,dx:0,dy:0};
|
|
:panelState = _loadPanelState();
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- _loadPanelState() --------
|
|
partition "_loadPanelState()" #FFF6D1 {
|
|
:_loadPanelState;
|
|
:JSON.parse(localStorage.getItem(STORAGE_KEYS.panel)||'{}');\ncatch -> {};
|
|
:return state;
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- _savePanelState(partial) --------
|
|
partition "_savePanelState(partial)" #FFE1DB {
|
|
:_savePanelState;
|
|
:merged = {...panelState, ...partial};\npanelState=merged; localStorage.setItem(..., JSON.stringify(merged));
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- flashBtn(btn, label, ms) --------
|
|
partition "flashBtn(btn, label, ms)" #DCF9EE {
|
|
:flashBtn;
|
|
:guard !btn; disable; temp label ✓; fade; setTimeout restore;
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- toast(msg, ms) --------
|
|
partition "toast(msg, ms)" #FFE6F0 {
|
|
:toast;
|
|
:guard !panel; create floating div; append; auto-remove;
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- copyLast(n=50) --------
|
|
partition "copyLast(n=50)" #E6F3FF {
|
|
:copyLast;
|
|
:lines = logger.getRecentLogs(n);\ntry navigator.clipboard.writeText -> info + toast;\ncatch -> _fallbackCopy(lines);
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- _fallbackCopy(text, err?) --------
|
|
partition "_fallbackCopy(text, err?)" #F0E6FA {
|
|
:_fallbackCopy;
|
|
:overlay+textarea UI; select+focus; Close button;\nwarn on missing Clipboard API;
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- mount() --------
|
|
partition "mount()" #E7FAF7 {
|
|
:mount;
|
|
:guard document.body else defer;\nif !cfg.debug.showPanel return;
|
|
:build root DOM (header/tabs/controls/bodies);\nappend to body;\nset refs; _wireControls(); _startLogRefresh();\npost-mount log info;
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- _wireControls() --------
|
|
partition "_wireControls()" #FFF2E7 {
|
|
:_wireControls;
|
|
:bind level select; copy buttons; pause/resume; queue clear;\nSTOP; tabs; collapse; dragging; clear history;\nfeature toggles; number inputs; bridge key;\nconfig JSON save/reset;
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- _loadToolsPanel() --------
|
|
partition "_loadToolsPanel()" #E7F7FF {
|
|
:_loadToolsPanel;
|
|
:sync toggles/nums from cfg;\nmask bridge key; sanitize config JSON (mask bridgeKey);
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- _startLogRefresh() --------
|
|
partition "_startLogRefresh()" #FFF7E7 {
|
|
:_startLogRefresh;
|
|
:renderLogs() each second: show tail of buffer or hints;\nautoscroll;
|
|
kill
|
|
}
|
|
fork again
|
|
' -------- bootstrap --------
|
|
partition "bootstrap (new + mount)" #F7E7FF {
|
|
:panel = new DebugPanel();\nif DOMContentLoaded pending -> add listener -> mount();\nelse mount();\nwindow.AI_REPO_DEBUG_PANEL = panel;
|
|
kill
|
|
}
|
|
end fork
|
|
@enduml
|
|
|
|
' ==== METHOD: constructor() ================================================
|
|
@startuml
|
|
title debug-panel:constructor(): \n Initialize DOM refs, drag state, and persisted panel state
|
|
|
|
participant "Caller" as CL
|
|
participant "constructor()" as CTOR
|
|
participant "_loadPanelState()" as LPS
|
|
|
|
activate CL
|
|
CL -> CTOR : new DebugPanel()
|
|
activate CTOR
|
|
CTOR -> CTOR : panel=null; bodyLogs=null; bodyTools=null; collapsed=false
|
|
CTOR -> CTOR : drag={active:false,dx:0,dy:0}
|
|
CTOR -> LPS : _loadPanelState()
|
|
LPS --> CTOR : state | {}
|
|
CTOR -> CTOR : panelState = state
|
|
CTOR --> CL : instance
|
|
deactivate CTOR
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: _loadPanelState() ============================================
|
|
@startuml
|
|
title debug-panel:_loadPanelState(): \n Read panel position/collapsed state from localStorage
|
|
|
|
participant "Caller" as CL
|
|
participant "_loadPanelState()" as LPS
|
|
participant "localStorage" as LS
|
|
participant "STORAGE_KEYS" as SK
|
|
|
|
activate CL
|
|
CL -> LPS : initial request
|
|
activate LPS
|
|
|
|
LPS -> SK : read STORAGE_KEYS.panel
|
|
SK --> LPS : key
|
|
LPS -> LS : getItem(key)
|
|
LS --> LPS : json | null
|
|
alt parse ok
|
|
LPS --> CL : JSON.parse(json) || {}
|
|
else parse error
|
|
LPS --> CL : {}
|
|
end
|
|
|
|
deactivate LPS
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: _savePanelState(partial) ======================================
|
|
@startuml
|
|
title debug-panel:_savePanelState(partial): \n Merge and persist panel state
|
|
|
|
participant "Caller" as CL
|
|
participant "_savePanelState(partial)" as SPS
|
|
participant "localStorage" as LS
|
|
participant "STORAGE_KEYS" as SK
|
|
|
|
activate CL
|
|
CL -> SPS : partial
|
|
activate SPS
|
|
|
|
SPS -> SPS : merged = {...panelState, ...partial}; panelState=merged
|
|
SPS -> SK : STORAGE_KEYS.panel
|
|
SK --> SPS : key
|
|
SPS -> LS : setItem(key, JSON.stringify(merged))
|
|
LS --> SPS : ok
|
|
|
|
SPS --> CL : (void)
|
|
deactivate SPS
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: flashBtn(btn, label='Done', ms=900) ===========================
|
|
@startuml
|
|
title debug-panel:flashBtn(btn, label, ms): \n Temporarily disable and relabel a button
|
|
|
|
participant "Caller" as CL
|
|
participant "flashBtn(btn,label,ms)" as FSH
|
|
participant "DOM" as DOM
|
|
participant "Timer" as TMR
|
|
|
|
activate CL
|
|
CL -> FSH : btn, label, ms
|
|
activate FSH
|
|
|
|
alt !btn
|
|
FSH --> CL : (void)
|
|
deactivate FSH
|
|
deactivate CL
|
|
return
|
|
end
|
|
|
|
FSH -> DOM : btn.disabled=true; store old text; set `${label} ✓`; opacity=.7
|
|
FSH -> TMR : setTimeout(ms)
|
|
TMR --> FSH : wake
|
|
FSH -> DOM : restore text; disabled=false; opacity=''
|
|
DOM --> FSH : ok
|
|
FSH --> CL : (void)
|
|
deactivate FSH
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: toast(msg, ms=1200) ==========================================
|
|
@startuml
|
|
title debug-panel:toast(msg, ms): \n Show a transient floating toast in panel
|
|
|
|
participant "Caller" as CL
|
|
participant "toast(msg, ms)" as TST
|
|
participant "DOM" as DOM
|
|
participant "Timer" as TMR
|
|
|
|
activate CL
|
|
CL -> TST : msg, ms
|
|
activate TST
|
|
|
|
alt !panel
|
|
TST --> CL : (void)
|
|
deactivate TST
|
|
deactivate CL
|
|
return
|
|
end
|
|
|
|
TST -> DOM : createElement('div') with styles; appendChild(panel)
|
|
DOM --> TST : elem
|
|
TST -> TMR : setTimeout(ms)
|
|
TMR --> TST : wake
|
|
TST -> DOM : elem.remove()
|
|
DOM --> TST : removed
|
|
|
|
TST --> CL : (void)
|
|
deactivate TST
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: copyLast(n=50) ===============================================
|
|
@startuml
|
|
title debug-panel:copyLast(n): \n Copy recent logs to clipboard or show manual copy overlay
|
|
|
|
participant "Caller" as CL
|
|
participant "copyLast(n)" as CPL
|
|
participant "Logger" as LOG
|
|
participant "Navigator.clipboard" as NCB
|
|
participant "toast(msg)" as TST
|
|
participant "_fallbackCopy(text,err?)" as FBC
|
|
|
|
activate CL
|
|
CL -> CPL : n
|
|
activate CPL
|
|
|
|
CPL -> LOG : getRecentLogs(n)
|
|
LOG --> CPL : lines (string)
|
|
|
|
CPL -> NCB : writeText(lines)
|
|
alt clipboard ok
|
|
NCB --> CPL : resolved
|
|
CPL -> LOG : info("Copied last n lines")
|
|
CPL -> TST : toast(`Copied last ${n} logs`)
|
|
TST --> CPL : shown
|
|
else clipboard error or unsupported
|
|
NCB --> CPL : rejected | (no API)
|
|
CPL -> FBC : _fallbackCopy(lines, error?)
|
|
FBC --> CPL : overlay shown
|
|
end
|
|
|
|
CPL --> CL : (void)
|
|
deactivate CPL
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: _fallbackCopy(text, originalError) ============================
|
|
@startuml
|
|
title debug-panel:_fallbackCopy(text, originalError): \n Fullscreen overlay with textarea for manual copy
|
|
|
|
participant "Caller" as CL
|
|
participant "_fallbackCopy(text,err?)" as FBC
|
|
participant "DOM" as DOM
|
|
participant "Logger" as LOG
|
|
|
|
activate CL
|
|
CL -> FBC : text, originalError?
|
|
activate FBC
|
|
|
|
FBC -> DOM : create overlay + panel + textarea + Close button; append to body
|
|
DOM --> FBC : mounted
|
|
FBC -> DOM : textarea.focus(); textarea.select()
|
|
DOM --> FBC : selected
|
|
FBC -> LOG : warn("Clipboard API unavailable", {error})
|
|
LOG --> FBC : ok
|
|
|
|
FBC --> CL : (void)
|
|
deactivate FBC
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: mount() =======================================================
|
|
@startuml
|
|
title debug-panel:mount(): \n Create panel DOM, wire controls, start log refresh, log ready
|
|
|
|
participant "Caller" as CL
|
|
participant "mount()" as MNT
|
|
participant "Config" as CFG
|
|
participant "DOM" as DOM
|
|
participant "_wireControls()" as WRC
|
|
participant "_startLogRefresh()" as LRF
|
|
participant "Logger" as LOG
|
|
participant "Timer" as TMR
|
|
|
|
activate CL
|
|
CL -> MNT : initial request
|
|
activate MNT
|
|
|
|
alt !document.body
|
|
MNT -> TMR : setTimeout(100)
|
|
TMR --> MNT : retry
|
|
MNT --> CL : (returns after scheduling)
|
|
else body present
|
|
MNT -> CFG : get('debug.showPanel')
|
|
CFG --> MNT : bool
|
|
alt disabled
|
|
MNT --> CL : (void)
|
|
else enabled
|
|
MNT -> DOM : build root HTML (header/tabs/controls/bodies)
|
|
DOM --> MNT : root
|
|
MNT -> DOM : appendChild(document.body, root)
|
|
DOM --> MNT : ok
|
|
MNT -> MNT : this.panel/root refs; bodyLogs/bodyTools
|
|
MNT -> WRC : _wireControls()
|
|
WRC --> MNT : wired
|
|
MNT -> LRF : _startLogRefresh()
|
|
LRF --> MNT : started
|
|
MNT -> TMR : setTimeout(100)
|
|
TMR --> MNT : tick
|
|
MNT -> LOG : info("Debug panel mounted...")
|
|
LOG --> MNT : ok
|
|
MNT -> LOG : info("Panel visible at: (...)")
|
|
end
|
|
MNT --> CL : (void)
|
|
end
|
|
|
|
deactivate MNT
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: _wireControls() ==============================================
|
|
@startuml
|
|
title debug-panel:_wireControls(): \n Bind all UI controls to config/logger/queue/history/actions
|
|
|
|
participant "Caller" as CL
|
|
participant "_wireControls()" as WRC
|
|
participant "Config" as CFG
|
|
participant "Logger" as LOG
|
|
participant "DOM" as DOM
|
|
participant "Queue" as QUE
|
|
participant "History" as HIS
|
|
participant "Paste" as PST
|
|
|
|
activate CL
|
|
CL -> WRC : initial request
|
|
activate WRC
|
|
|
|
' Log level selector
|
|
WRC -> DOM : select('.rc-level')
|
|
DOM --> WRC : sel
|
|
WRC -> CFG : get('debug.level')
|
|
CFG --> WRC : currentLevel
|
|
WRC -> LOG : trace(`[Debug Panel] Current log level: ${currentLevel}`)
|
|
WRC -> DOM : sel.onchange -> LOG.setLevel(newLevel) + LOG.info(...)
|
|
|
|
' Copy buttons
|
|
WRC -> DOM : .rc-copy / .rc-copy-200 (onclick -> copyLast(n) + flashBtn)
|
|
DOM --> WRC : wired
|
|
|
|
' Pause/Resume runtime
|
|
WRC -> DOM : .rc-pause (onclick -> toggle cfg.runtime.paused; flashBtn; toast; log)
|
|
DOM --> WRC : wired
|
|
|
|
' Queue clear
|
|
WRC -> DOM : .rc-queue-clear (onclick -> AI_REPO_QUEUE.clear(); flashBtn; toast; log.warn)
|
|
DOM --> WRC : wired
|
|
|
|
' Emergency STOP
|
|
WRC -> DOM : .rc-stop (onclick -> AI_REPO_STOP(); flashBtn; toast; log.warn)
|
|
DOM --> WRC : wired
|
|
|
|
' Tabs + Tools load
|
|
WRC -> DOM : tabs ('Logs'/'Tools & Settings'); switch bodies; call _loadToolsPanel()
|
|
DOM --> WRC : wired
|
|
|
|
' Collapse toggle
|
|
WRC -> DOM : .rc-collapse (onclick -> toggle collapsed; save state)
|
|
DOM --> WRC : wired
|
|
|
|
' Drag header
|
|
WRC -> DOM : .rc-header (mousedown -> track; mousemove -> set left/top; mouseup -> _savePanelState)
|
|
DOM --> WRC : wired
|
|
|
|
' Clear History
|
|
WRC -> DOM : .rc-clear-history (onclick -> HIS.clear(); GM_notification?; flashBtn; toast; log)
|
|
DOM --> WRC : wired
|
|
|
|
' Toggles (checkboxes)
|
|
WRC -> DOM : .rc-toggle (bind cfg.get/set; toast; log.info)
|
|
DOM --> WRC : wired
|
|
|
|
' Numeric inputs
|
|
WRC -> DOM : .rc-num (bind cfg.get; on change -> parseInt -> cfg.set; toast; log.info)
|
|
DOM --> WRC : wired
|
|
|
|
' Bridge key save/clear
|
|
WRC -> DOM : .rc-save-bridge-key / .rc-clear-bridge-key (onclick -> cfg.set('api.bridgeKey', val|''); mask input; flashBtn; toast; log)
|
|
DOM --> WRC : wired
|
|
|
|
' Config JSON save/reset
|
|
WRC -> DOM : .rc-save-json (parse JSON; delete meta/runtime; deep-set cfg; refresh tools; toast/log), .rc-reset-defaults (confirm -> localStorage.removeItem(cfgKey) -> reload)
|
|
DOM --> WRC : wired
|
|
|
|
WRC --> CL : (void)
|
|
deactivate WRC
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: _loadToolsPanel() ============================================
|
|
@startuml
|
|
title debug-panel:_loadToolsPanel(): \n Populate controls from cfg and render sanitized JSON
|
|
|
|
participant "Caller" as CL
|
|
participant "_loadToolsPanel()" as LTP
|
|
participant "Config" as CFG
|
|
participant "DOM" as DOM
|
|
|
|
activate CL
|
|
CL -> LTP : initial request
|
|
activate LTP
|
|
|
|
' Toggles and nums
|
|
LTP -> DOM : queryAll('.rc-toggle') / queryAll('.rc-num')
|
|
DOM --> LTP : lists
|
|
LTP -> CFG : get(key) for each
|
|
CFG --> LTP : values
|
|
LTP -> DOM : set .checked / .value accordingly
|
|
|
|
' Bridge key masked
|
|
LTP -> CFG : get('api.bridgeKey')
|
|
CFG --> LTP : key | ''
|
|
LTP -> DOM : .rc-bridge-key.value = key ? '••••••••' : ''
|
|
|
|
' Config JSON sanitized
|
|
LTP -> CFG : config (object)
|
|
CFG --> LTP : dump
|
|
LTP -> LTP : sanitized = deep clone; if api.bridgeKey -> mask
|
|
LTP -> DOM : .rc-json.value = JSON.stringify(sanitized, null, 2)
|
|
|
|
LTP --> CL : (void)
|
|
deactivate LTP
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: _startLogRefresh() ===========================================
|
|
@startuml
|
|
title debug-panel:_startLogRefresh(): \n Tail logger buffer every second and autoscroll
|
|
|
|
participant "Caller" as CL
|
|
participant "_startLogRefresh()" as LRF
|
|
participant "Logger" as LOG
|
|
participant "DOM" as DOM
|
|
participant "Timer" as TMR
|
|
|
|
activate CL
|
|
CL -> LRF : initial request
|
|
activate LRF
|
|
|
|
LRF -> LRF : define renderLogs()
|
|
LRF -> TMR : setInterval(renderLogs, 1000)
|
|
TMR --> LRF : id
|
|
LRF -> LRF : renderLogs()
|
|
|
|
' renderLogs inner flow
|
|
LRF -> LOG : buffer?
|
|
LOG --> LRF : buffer | undefined
|
|
alt logger not ready
|
|
LRF -> DOM : bodyLogs.innerHTML = "Logger not initialized yet..."
|
|
else ready
|
|
LRF -> LRF : rows = buffer.slice(-80)
|
|
alt rows empty
|
|
LRF -> DOM : "No logs yet. Waiting for activity..."
|
|
else rows
|
|
LRF -> DOM : bodyLogs.innerHTML = rows.map(...).join('')
|
|
LRF -> DOM : bodyLogs.scrollTop = bodyLogs.scrollHeight
|
|
end
|
|
end
|
|
|
|
LRF --> CL : (void)
|
|
deactivate LRF
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== METHOD: bootstrap (instance + mount) =================================
|
|
@startuml
|
|
title debug-panel:bootstrap: \n Instantiate panel and mount after DOM is ready
|
|
|
|
participant "Caller" as CL
|
|
participant "bootstrap" as BOOT
|
|
participant "DOM" as DOM
|
|
participant "DebugPanel()" as CTOR
|
|
participant "mount()" as MNT
|
|
|
|
activate CL
|
|
CL -> BOOT : initial load
|
|
activate BOOT
|
|
|
|
BOOT -> CTOR : new DebugPanel()
|
|
CTOR --> BOOT : panel
|
|
BOOT -> DOM : document.readyState
|
|
DOM --> BOOT : 'loading' | 'interactive/complete'
|
|
alt loading
|
|
BOOT -> DOM : addEventListener('DOMContentLoaded', () => panel.mount())
|
|
DOM --> BOOT : listener added
|
|
else ready
|
|
BOOT -> MNT : panel.mount()
|
|
MNT --> BOOT : mounted
|
|
end
|
|
|
|
BOOT -> DOM : window.AI_REPO_DEBUG_PANEL = panel
|
|
BOOT --> CL : (void)
|
|
deactivate BOOT
|
|
deactivate CL
|
|
@enduml
|
|
|
|
' ==== LEGEND ===============================================================
|
|
@startuml
|
|
legend bottom
|
|
== debug-panel 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/Per-method Sequence diagrams for each exported or significant internal function.
|
|
|
|
• Sequence conventions:
|
|
1) First participant is the external caller (use "Caller" or a concrete origin).
|
|
2) Do NOT add a module/class lifeline; the name appears in the title only.
|
|
3) Include every directly-called method or subsystem as a participant
|
|
(e.g., "mount()", "_wireControls()", "copyLast()", "_fallbackCopy()", "toast()", "flashBtn()", "Queue", "History", "Logger", "Config", "Timer", "DOM", "localStorage", "STORAGE_KEYS").
|
|
4) Prefer simple messages; Use --> for returns; -> for calls.
|
|
5) Use activate/deactivate as you see fit for clarity.
|
|
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., masked bridge key, JSON sanitize, tail size ~80 rows).
|
|
|
|
• Color palette (soft pastels)
|
|
• Use --> for returns; -> for calls.
|
|
• Participants use quoted method names for internals (e.g., "_loadToolsPanel()"), and plain nouns for systems ("DOM", "Timer", "localStorage").
|
|
• Keep this legend at the end of the file to standardize edits.
|
|
endlegend
|
|
@enduml
|