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

446 lines
12 KiB
Plaintext

' ===================================================================
' File: ConfigManager.puml
' Purpose: Single source of truth for class-level activity and per-method sequences.
' Edit rules: Follow the legend at bottom; preserve VIEW/METHOD anchors for automation.
' ===================================================================
' (Optional) neutral defaults — typography/layout only (keeps your colors intact)
skinparam Shadowing false
skinparam SequenceMessageAlign center
skinparam SequenceLifeLineBorderColor #666666
skinparam SequenceLifeLineBorderThickness 1
' ==== VIEW: Branch Flow (full class) ==========================================
@startuml ConfigManager
title ConfigManager — Branch Flow (full class)
start
:ConfigManager;
' Fan-out to each method
fork
' -------- constructor() --------
partition "constructor()" #E7FAE3 {
:constructor();
:this.config = load();
kill
}
fork again
' -------- load() --------
partition "load()" #FFF6D1 {
:load();
:raw = localStorage.getItem(STORAGE_KEYS.cfg);
if (raw is null/empty?) then (yes)
:config = deepClone(DEFAULT_CONFIG);
kill
else (no)
:try parse = JSON.parse(raw);
if (parse ok?) then (yes)
:saved = parse;
:config = mergeConfigs(DEFAULT_CONFIG, saved);
kill
else (no / parse error)
:config = deepClone(DEFAULT_CONFIG);
kill
endif
endif
}
fork again
' -------- save() --------
partition "save()" #FFE1DB {
:save();
:persistable = deepClone(config);
if (persistable has runtime?) then (yes)
:delete persistable.runtime;
endif
:try json = JSON.stringify(persistable);
if (stringify ok?) then (yes)
:localStorage.setItem(STORAGE_KEYS.cfg, json);
kill
else (no / stringify error)
:/* log/notify failure */;
kill
endif
}
fork again
' -------- get(keyPath) --------
partition "get(keyPath)" #DCF9EE {
:get(keyPath);
:parts = keyPath.split('.');
:node = config;
while (more parts?)
:p = next part;
if (node[p] exists?) then (yes)
:node = node[p];
else (no)
:return undefined;
kill
endif
endwhile
:return node;
kill
}
fork again
' -------- set(keyPath, value) --------
partition "set(keyPath, value)" #FFE6F0 {
:set(keyPath, value);
:parts = keyPath.split('.');
:node = config;
while (more parts?)
:p = next part;
if (node[p] exists?) then (yes)
:node = node[p];
else (no)
:node[p] = {};
:node = node[p];
endif
endwhile
:assign value at final key;
:save();
kill
}
fork again
' -------- mergeConfigs(defaults, saved) --------
partition "mergeConfigs(defaults, saved)" #E6F3FF {
:mergeConfigs(defaults, saved);
:result = deepClone(defaults);
if (saved is object?) then (yes)
:for each key k in saved;
while (keys left?)
:k = next key;
if (k == "runtime"?) then (yes)
:skip;
else (no)
if (both result[k] and saved[k] are plain objects?) then (yes)
:result[k] = mergeConfigs(result[k], saved[k]);
else (no)
:result[k] = deepClone(saved[k]);
endif
endif
endwhile
else (no)
:/* nothing to merge */;
endif
:return result;
kill
}
fork again
' -------- deepClone(o) --------
partition "deepClone(o)" #F0E6FA {
:deepClone(o);
if (o is null or primitive?) then (yes)
:return o;
kill
else (no)
if (o is Array?) then (yes)
:clone = [];
:for each item -> push( deepClone(item) );
:return clone;
kill
else (no)
:clone = {};
:for each key -> clone[key] = deepClone(o[key]);
:return clone;
kill
endif
endif
}
end fork
@enduml
' ==== METHOD: constructor() ================================================
@startuml constructor
title ConfigManager:constructor(): \n Populate this.config at instantiation
actor Page as PG
participant "constructor()" as CTOR
participant "load()" as LD
PG -> CTOR : new ConfigManager()
activate CTOR
CTOR -> LD : populate this.config
LD --> CTOR : config object
CTOR --> PG : this.config set
deactivate CTOR
@enduml
' ==== METHOD: load() =======================================================
@startuml load
title ConfigManager:load(): \n Read from localStorage, parse+merge or fallback to defaults
participant "Caller" as CL
participant "load()" as LD
participant "localStorage" as LS
participant "mergeConfigs()" as MC
participant "deepClone()" as DC
activate CL
activate LD
CL -> LD : initial request
activate LS
LD -> LS : getItem(STORAGE_KEYS.cfg)
LS --> LD : getItem(STORAGE_KEYS.cfg)
deactivate LS
alt STORAGE_KEYS.cfg (Empty)
activate DC
LD -> DC : deepClone(DEFAULT_CONFIG)
LD <-- DC : defaults clone
deactivate DC
LD --> CL : return defaults
else STORAGE_KEYS.cfg (Not empty)
LD --> LD : try parse STORAGE_KEYS.cfg
alt parse ok
LD --> LD : saved = parsed
activate MC
LD -> MC : mergeConfigs(DEFAULT_CONFIG, saved)
MC --> LD : merged config
deactivate MC
LD --> CL : return merged config
else parse error
activate DC
LD -> DC : deepClone(DEFAULT_CONFIG)
LD <-- DC : defaults clone
deactivate DC
LD --> CL : return defaults
end
end
@enduml
' ==== METHOD: save() =======================================================
@startuml save
title ConfigManager:save(): \n Strip runtime, stringify, persist to localStorage
participant "Caller" as CL
participant "save()" as SV
participant "deepClone()" as DC
participant "JSON" as JS
participant "localStorage" as LS
participant "Console" as CLG
activate CL
CL -> SV : initial request
deactivate CL
activate SV
activate DC
SV -> DC : deepClone(config)
DC --> SV : persistable clone
deactivate DC
SV -> SV : delete any persistable.runtime
activate JS
SV -> JS : JSON.stringify(persistable)
alt stringify ok
JS --> SV : json string
activate LS
SV -> LS : setItem(STORAGE_KEYS.cfg, json)
deactivate LS
else stringify error
JS --> SV : error
activate CLG
SV -> CLG : log/notify failure
deactivate CLG
end
deactivate JS
@enduml
' ==== METHOD: get(keyPath) ================================================
@startuml get
title ConfigManager:get(keyPath): \n Resolve a dotted path or return undefined
participant "Caller" as CL
participant "get(keyPath)" as GET
activate CL
CL -> GET : initial request (keyPath)
activate GET
GET -> GET : parts = keyPath.split('.'); node = config
loop for each part
alt node has part
GET -> GET : node = node[part]
else missing segment
GET --> CL : undefined
end
end
GET --> CL : value (final node)
deactivate GET
deactivate CL
@enduml
' ==== METHOD: set(keyPath, value) =========================================
@startuml set
title ConfigManager:set(keyPath, value): \n Create missing path segments, assign, then persist
participant "Caller" as CL
participant "set(keyPath, value)" as SET
participant "save()" as SV
activate CL
CL -> SET : initial request (keyPath, value)
activate SET
SET -> SET : parts = keyPath.split('.'); node = config
loop for each part (except last)
alt node has part
SET -> SET : node = node[part]
else missing
SET -> SET : node[part] = {}; node = node[part]
end
end
SET -> SET : assign value at final key
SET -> SV : save()
SV --> SET : persisted
SET --> CL : done
deactivate SET
deactivate CL
@enduml
' ==== METHOD: mergeConfigs(defaults, saved) ================================
@startuml mergeConfigs
title ConfigManager:mergeConfigs(defaults, saved): \n Deep merge saved over defaults (skip runtime)
participant "Caller" as CL
participant "mergeConfigs()" as MC
participant "deepClone()" as DC
activate CL
CL -> MC : initial request (defaults, saved)
activate MC
MC -> DC : deepClone(defaults)
DC --> MC : result (clone of defaults)
alt saved is plain object
loop for each key k in saved
alt k == "runtime"
MC -> MC : skip key
else not runtime
alt both result[k] and saved[k] are plain objects
MC -> MC : result[k] = mergeConfigs(result[k], saved[k]) ' recursive
else overwrite
MC -> DC : deepClone(saved[k])
DC --> MC : cloned value
MC -> MC : result[k] = cloned value
end
end
end
else saved not object
MC -> MC : nothing to merge
end
MC --> CL : result
deactivate MC
deactivate CL
@enduml
' ==== METHOD: deepClone(o) ================================================
@startuml deepClone
title ConfigManager:deepClone(o): \n Structural clone for arrays/objects; primitives by value
participant "Caller" as CL
participant "deepClone()" as DC
activate CL
CL -> DC : initial request (o)
activate DC
alt o is null or primitive
DC --> CL : o
deactivate DC
deactivate CL
return
else non-primitive
alt Array
DC -> DC : clone = []
loop each item
DC -> DC : clone.push( deepClone(item) )
end
DC --> CL : clone
else Object
DC -> DC : clone = {}
loop each key
DC -> DC : clone[key] = deepClone(o[key])
end
DC --> CL : clone
end
end
deactivate DC
deactivate CL
@enduml
' ==== LEGEND ===============================================================
@startuml legend
legend bottom
== Config UML Style Guide (for future edits) ==
• Scope: One .puml per class or file. Keep two views:
(1) Activity "Branch Flow" for all methods for the class or functions in the file.
- (partitions + soft colors),
(2) Per-method Sequence diagrams for each of the methods or functions.
• Sequence conventions:
1) First participant is the external caller (use "Caller" or "Page").
2) Do NOT add the class lifeline unless needed (constructor). Class name appears in title only.
3) Include every directly-called method or subsystem as a participant
(e.g., "load()", "mergeConfigs()", "deepClone()", "JSON", "localStorage", "Console").
4) Prefer simple messages.
5) Use activate/deactivate for the method under focus and key collaborators.
6) Use alt blocks only when branches meaningfully change the message flow.
For trivial checks (e.g., delete runtime if exists), inline the action.
7) Titles: "ClassName:method(): \n Detailed description of the flow".
• Activity view conventions:
A) Start with Class(or filename) node then fork partitions for each method or function.
B) One partition per method; soft background color; terminate branches with 'kill'.
C) Keep wording aligned with code (e.g., "deepClone(DEFAULT_CONFIG)", "mergeConfigs(...)").
• Color palette (soft pastels)
• Use --> for returns; -> for calls.
• Participants use quoted method names for internals (e.g., "save()"), and plain nouns for systems ("JSON", "localStorage", "Console").
• When modifying this file keep this legend at the end of the file to standardize edits.
UML_Example
------------------------------------------
title ClassName:methodName(args): \n Detailed description of what this method does
participant "Caller" as CL
participant "methodName()" as M ' the method under focus
' Add collaborators as needed:
' participant "SomeDependency" as DEP
' participant "AnotherMethod()" as AM
' participant "JSON" as JS
' participant "localStorage" as LS
activate CL
CL -> M : initial request (args)
activate M
' -- inner flow (keep alt blocks only if they clarify) --
' Example pattern:
activate LS
' M -> LS : getItem(KEY)
' LS --> M : value
deactivate LS
' alt branch condition
activate AM
' M -> AM : call anotherMethod(...)
' AM --> M : result
deactivate AM
' else other branch
activate DEP
' M -> DEP : do something
' DEP -> M : ok
deactivate DEP
' end
' Return to caller
M -> CL : return value
deactivate M
deactivate CL
---------------------------------------------
endlegend
@enduml