AI-Repo-Commander/Docs/diagrams/storage.puml

419 lines
11 KiB
Plaintext

' ===================================================================
' File: storage.puml
' Purpose: Single source of truth for module-level activity + per-method sequences.
' Module: storage.js — Conversation-aware de-duplication via localStorage.
' 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 (storage.js) =======================================
@startuml
title storage.js — Branch Flow (full module)
start
:storage;
fork
' -------- ConversationHistory.constructor() --------
partition "ConversationHistory.constructor()" #E7FAE3 {
:constructor;
:conversationId = _getConversationId();\nkey = `ai_rc:conv:${conversationId}:processed`;
:cache = _load(); _cleanupExpired();
kill
}
fork again
' -------- _getConversationId() --------
partition "_getConversationId()" #FFF6D1 {
:_getConversationId;
:host = location.hostname.replace('chat.openai.com','chatgpt.com');\nreturn `${host}:${location.pathname||'/'}`;
kill
}
fork again
' -------- _load() / _save() --------
partition "_load() / _save()" #FFE1DB {
:_load -> JSON.parse(localStorage.getItem(key)||'{}')\n(catch -> {});
:_save -> localStorage.setItem(key, JSON.stringify(cache))\n(catch -> logger.warn(...));
kill
}
fork again
' -------- isProcessed(el, idx=0) --------
partition "isProcessed(el, idx=0)" #DCF9EE {
:isProcessed;
:fp = _fingerprint(el, idx);\nreturn cache.hasOwnProperty(fp);
kill
}
fork again
' -------- markProcessed(el, idx=0) --------
partition "markProcessed(el, idx=0)" #FFE6F0 {
:markProcessed;
:fp = _fingerprint(el, idx);\ncache[fp] = Date.now(); _save();\nif cfg.ui.showExecutedMarker -> _mark(el);
kill
}
fork again
' -------- _fingerprint(el, idx) --------
partition "_fingerprint(el, idx)" #E6F3FF {
:_fingerprint;
:base = (window.AI_REPO_STABLE_FINGERPRINT? stable(el)\n : window.AI_REPO_FINGERPRINT? compat(el)\n : _hash((el.textContent||'').slice(0,1000)));
:return `${base}|idx:${idx}`;
kill
}
fork again
' -------- _hash(str) --------
partition "_hash(str)" #F0E6FA {
:_hash;
:djb2-xor over up to 1000 chars;\nreturn unsigned base36;
kill
}
fork again
' -------- _mark(el) --------
partition "_mark(el)" #E7FAF7 {
:_mark;
:el.style.borderLeft = '3px solid #10B981' (try/catch);
kill
}
fork again
' -------- _cleanupExpired() --------
partition "_cleanupExpired()" #FFF2E7 {
:_cleanupExpired;
:ttl = 30d; now = Date.now();\nfor (k,ts in cache) if !ts || now - ts > ttl -> delete;\nif dirty -> _save();
kill
}
fork again
' -------- clear() --------
partition "clear()" #E7F7FF {
:clear;
:cache = {}; _save();
kill
}
fork again
' -------- bootstrap (global) --------
partition "bootstrap (global)" #FFF7E7 {
:window.AI_REPO_HISTORY = new ConversationHistory();
kill
}
end fork
@enduml
' ==== METHOD: ConversationHistory.constructor() ============================
@startuml
title storage:constructor(): \n Build per-conversation key, load cache, prune expired
participant "Caller" as CL
participant "constructor()" as CTOR
participant "_getConversationId()" as GID
participant "_load()" as LOAD
participant "_cleanupExpired()" as CLEAN
activate CL
CL -> CTOR : new ConversationHistory()
activate CTOR
CTOR -> GID : _getConversationId()
GID --> CTOR : conversationId
CTOR -> CTOR : key = `ai_rc:conv:${conversationId}:processed`
CTOR -> LOAD : _load()
LOAD --> CTOR : cache object
CTOR -> CLEAN : _cleanupExpired()
CLEAN --> CTOR : done
CTOR --> CL : instance
deactivate CTOR
deactivate CL
@enduml
' ==== METHOD: _getConversationId() ========================================
@startuml
title storage:_getConversationId(): \n host+path key (normalize chat.openai.com → chatgpt.com)
participant "Caller" as CL
participant "_getConversationId()" as GID
participant "Location" as LOC
activate CL
CL -> GID : initial request
activate GID
GID -> LOC : read location.hostname, location.pathname
LOC --> GID : hostname, pathname
GID -> GID : host = hostname.replace('chat.openai.com','chatgpt.com')
GID --> CL : `${host}:${pathname || '/'}`
deactivate GID
deactivate CL
@enduml
' ==== METHOD: _load() / _save() ===========================================
@startuml
title storage:_load() / _save(): \n Safely read/write JSON cache in localStorage
participant "Caller" as CL
participant "_load()" as LOAD
participant "_save()" as SAVE
participant "localStorage" as LS
participant "Logger" as LOG
' _load()
activate CL
CL -> LOAD : initial request
activate LOAD
LOAD -> LS : getItem(key)
LS --> LOAD : json | null
LOAD -> LOAD : JSON.parse(json||'{}') catch -> {}
LOAD --> CL : cache object
deactivate LOAD
' _save()
CL -> SAVE : cache
activate SAVE
SAVE -> LS : setItem(key, JSON.stringify(cache))
alt success
LS --> SAVE : ok
SAVE --> CL : done
else error
LS --> SAVE : throw e
SAVE -> LOG : warn('Failed to save history cache', {error:e.message})
LOG --> SAVE : logged
SAVE --> CL : done
end
deactivate SAVE
deactivate CL
@enduml
' ==== METHOD: isProcessed(el, idx=0) ======================================
@startuml
title storage:isProcessed(el, idx): \n Check cache using fingerprint+index key
participant "Caller" as CL
participant "isProcessed(el,idx)" as ISP
participant "_fingerprint(el,idx)" as FP
activate CL
CL -> ISP : el, idx
activate ISP
ISP -> FP : _fingerprint(el, idx)
FP --> ISP : fp
ISP -> ISP : return Object.prototype.hasOwnProperty.call(cache, fp)
ISP --> CL : true/false
deactivate ISP
deactivate CL
@enduml
' ==== METHOD: markProcessed(el, idx=0) =====================================
@startuml
title storage:markProcessed(el, idx): \n Record timestamp, persist, optionally mark DOM
participant "Caller" as CL
participant "markProcessed(el,idx)" as MK
participant "_fingerprint(el,idx)" as FP
participant "_save()" as SAVE
participant "_mark(el)" as MRK
participant "Config" as CFG
activate CL
CL -> MK : el, idx
activate MK
MK -> FP : _fingerprint(el, idx)
FP --> MK : fp
MK -> MK : cache[fp] = Date.now()
MK -> SAVE : _save()
SAVE --> MK : ok
MK -> CFG : get('ui.showExecutedMarker')
CFG --> MK : bool
alt true
MK -> MRK : _mark(el)
MRK --> MK : done
end
MK --> CL : (void)
deactivate MK
deactivate CL
@enduml
' ==== METHOD: _fingerprint(el, idx) ========================================
@startuml
title storage:_fingerprint(el, idx): \n Use stable FP if available; else compat; else inline hash
participant "Caller" as CL
participant "_fingerprint(el,idx)" as FP
participant "AI_REPO_STABLE_FINGERPRINT" as STAB
participant "AI_REPO_FINGERPRINT" as COMP
participant "_hash(str)" as HSH
activate CL
CL -> FP : el, idx
activate FP
FP -> STAB : exists?
STAB --> FP : yes/no
alt stable available
FP -> STAB : STABLE_FINGERPRINT(el)
STAB --> FP : base
else no stable
FP -> COMP : exists?
COMP --> FP : yes/no
alt compat available
FP -> COMP : FINGERPRINT(el)
COMP --> FP : base
else none
FP -> FP : text = (el.textContent||'').slice(0,1000)
FP -> HSH : _hash(text)
HSH --> FP : base
end
end
FP --> CL : `${base}|idx:${idx}`
deactivate FP
deactivate CL
@enduml
' ==== METHOD: _hash(str) ===================================================
@startuml
title storage:_hash(str): \n djb2-xor over up to 1000 chars → base36
participant "Caller" as CL
participant "_hash(str)" as HSH
activate CL
CL -> HSH : str
activate HSH
HSH -> HSH : h=5381; for i<min(len,1000): h=((h<<5)+h)^charCode
HSH --> CL : (h>>>0).toString(36)
deactivate HSH
deactivate CL
@enduml
' ==== METHOD: _mark(el) ====================================================
@startuml
title storage:_mark(el): \n Visual left border to indicate executed command
participant "Caller" as CL
participant "_mark(el)" as MRK
participant "DOM" as DOM
activate CL
CL -> MRK : el
activate MRK
MRK -> DOM : el.style.borderLeft = '3px solid #10B981' (try/catch)
DOM --> MRK : ok | ignored
MRK --> CL : (void)
deactivate MRK
deactivate CL
@enduml
' ==== METHOD: _cleanupExpired() ============================================
@startuml
title storage:_cleanupExpired(): \n TTL pruning (30 days) + conditional save
participant "Caller" as CL
participant "_cleanupExpired()" as CLN
participant "_save()" as SAVE
activate CL
CL -> CLN : initial request
activate CLN
CLN -> CLN : ttl=30*24*60*60*1000; now=Date.now(); dirty=false
loop for each [k, ts] in Object.entries(cache)
alt !ts or now - ts > ttl
CLN -> CLN : delete cache[k]; dirty=true
end
end
alt dirty
CLN -> SAVE : _save()
SAVE --> CLN : ok
end
CLN --> CL : (void)
deactivate CLN
deactivate CL
@enduml
' ==== METHOD: clear() ======================================================
@startuml
title storage:clear(): \n Reset cache and persist
participant "Caller" as CL
participant "clear()" as CLR
participant "_save()" as SAVE
activate CL
CL -> CLR : initial request
activate CLR
CLR -> CLR : cache = {}
CLR -> SAVE : _save()
SAVE --> CLR : ok
CLR --> CL : (void)
deactivate CLR
deactivate CL
@enduml
' ==== METHOD: bootstrap (global) ===========================================
@startuml
title storage:bootstrap: \n Expose ConversationHistory singleton on window
participant "Caller" as CL
participant "bootstrap" as BOOT
participant "ConversationHistory()" as CTOR
participant "window" as WIN
activate CL
CL -> BOOT : module load
activate BOOT
BOOT -> CTOR : new ConversationHistory()
CTOR --> BOOT : instance
BOOT -> WIN : window.AI_REPO_HISTORY = instance
WIN --> BOOT : ok
BOOT --> CL : (void)
deactivate BOOT
deactivate CL
@enduml
' ==== LEGEND ===============================================================
@startuml
legend bottom
== storage 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 helper.
• Sequence conventions:
1) First participant is the external caller (use "Caller" or a concrete origin).
2) Do NOT add a class lifeline; the module/class name appears in the title only.
3) Include every directly-called helper/system as a participant
(e.g., "_fingerprint()", "_hash()", "_mark()", "Config", "Logger", "localStorage", "Location").
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., 30-day TTL, base36 hashes, idx suffix, key prefix `ai_rc:conv:`).
• Color palette (soft pastels)
• Use --> for returns; -> for calls.
• Participants use quoted method names for internals; plain nouns for systems ("localStorage", "Location", "window").
• Keep this legend at the end of the file to standardize edits.
endlegend
@enduml