AI-Repo-Commander/Docs/diagrams/file_diagrams/response-buffer.puml

314 lines
9.3 KiB
Plaintext

' ===================================================================
' File: response-buffer.puml
' Purpose: Single source of truth for module-level activity + per-method sequences.
' Module: response-buffer.js — Buffer result chunks; split safely; enqueue paste ops.
' 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 (response-buffer.js) ===============================
@startuml
title response-buffer.js — Branch Flow (full module)
start
:response-buffer;
fork
' -------- chunkByLines(s, limit) --------
partition "chunkByLines(s, limit)" #E7FAE3 {
:chunkByLines;
:walk by soft breaks at \\n near limit;\nappend slice; advance;
:return chunks[];
kill
}
fork again
' -------- isSingleFence(s) --------
partition "isSingleFence(s)" #FFF6D1 {
:isSingleFence;
:/^```[^\\n]*\\n[\\s\\S]*\\n```$/ on trim(s);
:return boolean;
kill
}
fork again
' -------- splitRespectingFence(text, limit) --------
partition "splitRespectingFence(text, limit)" #FFE1DB {
:splitRespectingFence;
:if not single fence -> chunkByLines(text, limit);\nelse split inner with adjusted limit;\nre-wrap each in same fence;
:return chunks[];
kill
}
fork again
' -------- ResponseBuffer.constructor() --------
partition "ResponseBuffer.constructor()" #DCF9EE {
:constructor;
:pending=[]; timer=null; flushing=false;
kill
}
fork again
' -------- ResponseBuffer.push({label,content}) --------
partition "push({label,content})" #FFE6F0 {
:push;
:ignore if no content; pending.push({label,content});\n_schedule();
kill
}
fork again
' -------- ResponseBuffer._schedule() --------
partition "_schedule()" #E6F3FF {
:_schedule;
:clearTimeout(timer);\n timer = setTimeout(() => flush(), 500);
kill
}
fork again
' -------- ResponseBuffer._build() --------
partition "_build()" #F0E6FA {
:_build;
:showHeadings=true; concat "### label" + content blocks;\nreturn big string;
kill
}
fork again
' -------- ResponseBuffer.flush() --------
partition "flush()" #E7FAF7 {
:flush;
:guard: if flushing or empty -> return;\nflushing=true; toPaste=_build(); pending.length=0;
:if toPaste.length > limit -> splitRespectingFence(...);\n enqueue each Part i payload;\nelse enqueue whole payload;
:finally flushing=false;
kill
}
end fork
@enduml
' ==== METHOD: chunkByLines(s, limit) =======================================
@startuml
title response-buffer:chunkByLines(s, limit): \n Split on newline boundaries near the limit
participant "Caller" as CL
participant "chunkByLines(s, limit)" as CBL
activate CL
CL -> CBL : s, limit
activate CBL
CBL -> CBL : out=[]; start=0
loop while start < s.length
CBL -> CBL : soft = s.lastIndexOf('\\n', min(start+limit, s.length))
CBL -> CBL : end = (soft > start ? soft+1 : min(start+limit, s.length))
CBL -> CBL : out.push(s.slice(start,end)); start=end
end
CBL --> CL : chunks[]
deactivate CBL
deactivate CL
@enduml
' ==== METHOD: isSingleFence(s) =============================================
@startuml
title response-buffer:isSingleFence(s): \n Detect single fenced block (```lang ... ```)
participant "Caller" as CL
participant "isSingleFence(s)" as ISF
activate CL
CL -> ISF : s
activate ISF
ISF -> ISF : test /^```[^\\n]*\\n[\\s\\S]*\\n```$/ on s.trim()
ISF --> CL : true/false
deactivate ISF
deactivate CL
@enduml
' ==== METHOD: splitRespectingFence(text, limit) =============================
@startuml
title response-buffer:splitRespectingFence(text, limit): \n Chunk text, keeping fenced blocks intact
participant "Caller" as CL
participant "splitRespectingFence(text, limit)" as SRF
participant "isSingleFence(s)" as ISF
participant "chunkByLines(s, limit)" as CBL
activate CL
CL -> SRF : text, limit
activate SRF
SRF -> SRF : t = text.trim()
SRF -> ISF : isSingleFence(t)
ISF --> SRF : single (bool)
alt single == false
SRF -> CBL : chunkByLines(text, limit)
CBL --> SRF : chunks[]
SRF --> CL : chunks[]
else single == true
SRF -> SRF : m = /^```([^\\n]*)\\n([\\s\\S]*)\\n```$/.exec(t)\nlang=(m?.[1]||'text').trim(); inner=m?.[2]||''
SRF -> SRF : innerLimit = limit - 16 - lang.length
SRF -> CBL : chunkByLines(inner, innerLimit)
CBL --> SRF : innerChunks[]
SRF -> SRF : chunks = innerChunks.map(c => '```'+lang+'\\n'+c.replace(/\\n?$/,'\\n')+'```')
SRF --> CL : chunks
end
deactivate SRF
deactivate CL
@enduml
' ==== METHOD: ResponseBuffer.constructor() ==================================
@startuml
title response-buffer:constructor(): \n Initialize pending buffer, timer, and state
participant "Caller" as CL
participant "constructor()" as CTOR
activate CL
CL -> CTOR : new ResponseBuffer()
activate CTOR
CTOR -> CTOR : pending=[]; timer=null; flushing=false
CTOR --> CL : instance
deactivate CTOR
deactivate CL
@enduml
' ==== METHOD: ResponseBuffer.push({label, content}) =========================
@startuml
title response-buffer:push({label, content}): \n Collect a piece for later batched paste
participant "Caller" as CL
participant "push({label,content})" as PUSH
participant "_schedule()" as SCH
activate CL
CL -> PUSH : {label, content}
activate PUSH
alt !content
PUSH --> CL : (void)
deactivate PUSH
deactivate CL
return
end
PUSH -> PUSH : pending.push({label,content})
PUSH -> SCH : _schedule()
SCH --> PUSH : scheduled
PUSH --> CL : (void)
deactivate PUSH
deactivate CL
@enduml
' ==== METHOD: ResponseBuffer._schedule() ====================================
@startuml
title response-buffer:_schedule(): \n Debounce flush with a short timer
participant "Caller" as CL
participant "_schedule()" as SCH
participant "Timer" as TMR
activate CL
CL -> SCH : initial request
activate SCH
SCH -> TMR : clearTimeout(timer)
TMR --> SCH : cleared
SCH -> TMR : timer = setTimeout( flush, 500 )
TMR --> SCH : id
SCH --> CL : (void)
deactivate SCH
deactivate CL
@enduml
' ==== METHOD: ResponseBuffer._build() =======================================
@startuml
title response-buffer:_build(): \n Build a readable combined payload with headings
participant "Caller" as CL
participant "_build()" as BLD
activate CL
CL -> BLD : initial request
activate BLD
BLD -> BLD : showHeadings = true; parts=[]
loop for each {label,content} in pending
BLD -> BLD : if label -> parts.push('### '+label+'\\n')\nparts.push(String(content).trimEnd(), '')
end
BLD --> CL : parts.join('\\n')
deactivate BLD
deactivate CL
@enduml
' ==== METHOD: ResponseBuffer.flush() ========================================
@startuml
title response-buffer:flush(): \n Debounced paste: split if huge, enqueue parts on ExecutionQueue
participant "Caller" as CL
participant "flush()" as FLS
participant "_build()" as BLD
participant "splitRespectingFence(text, limit)" as SRF
participant "Queue" as QUE
participant "Paste.submitToComposer(text)" as PST
activate CL
CL -> FLS : initial request
activate FLS
' guard + state
FLS -> FLS : if (flushing || pending.length==0) return
FLS -> FLS : flushing = true
FLS -> BLD : _build()
BLD --> FLS : toPaste
FLS -> FLS : pending.length = 0
' branch by length limit
FLS -> FLS : limit = 250_000
alt toPaste.length > limit
FLS -> SRF : splitRespectingFence(toPaste, limit)
SRF --> FLS : chunks[]
loop for each c, i
FLS -> FLS : header = `### Part ${i+1}/${chunks.length}\\n`; payload = header + c
FLS -> QUE : push(async () => PST.submitToComposer(payload))
QUE --> FLS : queued
end
else within limit
FLS -> QUE : push(async () => PST.submitToComposer(toPaste))
QUE --> FLS : queued
end
FLS --> CL : (finally flushing=false)
deactivate FLS
deactivate CL
@enduml
' ==== LEGEND ===============================================================
@startuml
legend bottom
== response-buffer 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., "chunkByLines()", "isSingleFence()", "splitRespectingFence()", "push()", "_schedule()", "_build()", "flush()", "Queue", "Paste").
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., 250_000 limit, fence shape, headings format).
• Color palette (soft pastels)
• Use --> for returns; -> for calls.
• Participants use quoted method names for internals (e.g., "flush()"), and plain nouns for systems ("Queue", "Paste", "Timer").
• Keep this legend at the end of the file to standardize edits.
endlegend
@enduml