@startuml
' RBciAD — Global Architecture (component view)
skinparam defaultFontName Helvetica
skinparam rectangle {
  RoundCorner 12
  BorderColor #333333
  BackgroundColor #f8f9fa
}
skinparam packageStyle rectangle

title RBciAD — Global Architecture (Host process vs External nodes)

package "Host Process (Python / PyQt)" #e8f0fe {
  rectangle "UI (PyQt)\n• Node Graph Editor\n• LiveDisplay\n• Low-code Editor" as UI
  rectangle "Reactive Engine (RxPy)\n• NodeGraph / Edges\n• Propagation / caching" as RX #eaf7ea
  rectangle "Plugin Manager\n• Discovery (plugins/, custom_plugins/)\n• Registration / palette" as PM
  rectangle "Acquisition Manager\n• Sources\n• Ring Buffer\n• Event Bus" as ACQ
  rectangle "Nodes (Python in-process)\n• EEGReader, Filter, FeatureExtractor,\n  ClassifierTrainer, PredictionVisualizer" as NPY
  rectangle "Wrapper for External Nodes\n• JSON I/O (input.json/output.json)\n• Timeout / exit code / stderr tail" as WRAP #fff3cd
  rectangle "Metrics Logger\n• CSV events (TTFP, PARAM_CHANGE, FRAME,\n  SAMPLES_IN, CPU, RSS, counters)" as LOG
}

package "External Processes (per node)" #fff3cd {
  rectangle "External Node (C/C++/Rust/Node.js)\nreads input.json → writes output.json" as NEXT
}

package "Sources" {
  rectangle "EDF/BDF Reader (MNE)" as EDF
  rectangle "LSL Reader (pylsl)" as LSL
}

UI -down-> RX
RX -down-> NPY
RX -down-> WRAP
WRAP -down-> NEXT : spawn / JSON exchange
EDF -down-> ACQ
LSL -down-> ACQ
ACQ -right-> RX : chunks (n_s×n_ch, sr_hz,\nch_names, ts_src)\nWindowReady(win_s, hop_s)
UI -right-> LOG : renderer counters
ACQ -down-> LOG : SAMPLES_IN
RX -right-> LOG : PARAM_CHANGE, FRAME
UI -left-> PM : palette insert / refresh
PM -down-> NPY : discovered classes
PM -down-> WRAP : external node wrappers
RX -right-> UI : frames (LiveDisplay)

note bottom
Legend: solid arrows = data/control flow; dashed = spawn/IPC boundaries.
End-user code can live either as Python nodes (in-process) or
as compiled scripts/binaries (external process via JSON I/O).
end note
@enduml
