State Overview
WikiOnitViewer maintains the simplest state model among all wiki viewers: just one custom array plus inherited properties from the base class.
┌─────────────────────────────────────────────────┐
│ State Inventory │
├─────────────────────────────────────────────────┤
│ │
│ Own State: │
│ sessions: OnitSession[] │
│ │
│ Inherited State (from WikiBaseViewer): │
│ container: HTMLElement | null │
│ projectSlug: string │
│ category: string │
│ isWip: boolean │
│ settings: WikiViewerSettings │
│ currentDocument: string │
│ │
│ Total: 7 properties (6 inherited + 1 own) │
│ │
└─────────────────────────────────────────────────┘
Sessions Array
The only state property unique to OnIt:
private sessions : OnitSession [] = [];
Structure:
Array of parsed session objects
Sorted newest first (descending sortKey)
Populated during loadSessions()
Completely replaced on refresh()
Example:
sessions = [
{
path: "/full/path/2025-12-28-14-30-session.md" ,
name: "2025-12-28-14-30-session.md" ,
date: "2025-12-28" ,
time: "14:30" ,
sessionName: "session" ,
pattern: 3 ,
sortKey: "2025-12-28T14:30:00" ,
status: "active"
},
// ... more sessions
]
State Lifecycle
Initialization
┌─────────────────────────────────────────────────┐
│ Initialization State Changes │
├─────────────────────────────────────────────────┤
│ │
│ BEFORE init(): │
│ container: null │
│ sessions: [] │
│ isWip: true (default) │
│ │
│ ↓ init(container, projectSlug, "onit") │
│ │
│ AFTER init(): │
│ container: HTMLElement (assigned) │
│ sessions: [OnitSession, ...] │
│ isWip: true (unchanged) │
│ category: "onit" │
│ settings: loaded from localStorage │
│ │
└─────────────────────────────────────────────────┘
Toggle Transition
┌─────────────────────────────────────────────────┐
│ WIP/Wiki Toggle Effect │
├─────────────────────────────────────────────────┤
│ │
│ WIP Mode: │
│ isWip: true │
│ sessions: [WIP sessions from wip/onit/] │
│ │
│ ↓ User clicks [Wiki] button │
│ │
│ Wiki Mode: │
│ isWip: false │
│ sessions: [Wiki sessions from wiki/onit/] │
│ │
│ Note: Other properties unchanged │
│ │
└─────────────────────────────────────────────────┘
Refresh Cycle
┌─────────────────────────────────────────────────┐
│ Refresh State Changes │
├─────────────────────────────────────────────────┤
│ │
│ BEFORE refresh(): │
│ sessions: [old data - may be stale] │
│ │
│ ↓ refresh() called │
│ │
│ AFTER refresh(): │
│ sessions: [new data - fresh from filesystem] │
│ │
│ Note: Complete array replacement │
│ DOM fully re-rendered │
│ Accordion states LOST │
│ │
└─────────────────────────────────────────────────┘
State Dependencies
Path construction depends on three state properties:
getBasePath() Calculation:
// From WikiBaseViewer
const base = "~/Library/Application Support/monolex/protocols/niia/work" ;
const folder = isWip ? "wip" : "wiki" ;
return ` ${ base } / ${ projectSlug } / ${ folder } / ${ category } ` ;
Example:
projectSlug = "monolex-006"
isWip = true
category = "onit"
Result: ~/Library/.../work/monolex-006/wip/onit/
Changing any dependency changes the filesystem path and thus the loaded sessions.
Unidirectional Data Flow
State flows in one direction only:
┌─────────────────────────────────────────────────┐
│ Data Flow │
├─────────────────────────────────────────────────┤
│ │
│ User Action (click toggle/refresh) │
│ ↓ │
│ Event Handler │
│ ↓ │
│ State Update (isWip, sessions) │
│ ↓ │
│ Re-render (render() called) │
│ ↓ │
│ DOM Updated (innerHTML replaced) │
│ ↓ │
│ Event Listeners Attached (to new elements) │
│ ↓ │
│ (Loop back to User Action) │
│ │
│ No reverse flow: DOM → State │
│ │
└─────────────────────────────────────────────────┘
DOM vs JavaScript State
Clear separation between persistent state and ephemeral UI state:
JavaScript State (this.*):
sessions: OnitSession[] (source of truth)
isWip: boolean
settings: ViewerSettings
Persists across renders
DOM State (in elements):
Accordion expanded/collapsed
Content loaded (data-loaded)
Arrow rotation
Scroll position
Lost on re-render
Relationship:
JS state is source of truth for data
DOM state is ephemeral (recreated on render)
Re-render regenerates DOM from JS state
No two-way binding
State Reset Pattern
Complete replacement on refresh:
async refresh (): Promise < void > {
await this.loadSessions(); // REPLACES this.sessions
this.render(); // Re-renders entire DOM
}
Effect:
No incremental updates
No diffing/patching
Guaranteed consistency
Accordion states lost
Trade-offs:
Simplicity (SMPC principle)
Easy to reason about
Potential performance impact on large lists
Loses UI state
Memory Management
Automatic cleanup via garbage collection:
On refresh():
this.sessions = new array → Old array becomes unreachable
container.innerHTML = new HTML → Old DOM nodes removed
JavaScript GC cleans up old arrays
Browser GC cleans up old DOM nodes
Event listeners on old nodes auto-removed
No manual cleanup needed. No memory leaks from orphaned listeners.
Settings Management
User preferences loaded from localStorage:
Settings Interface:
interface WikiViewerSettings {
fontSize : string ; // "12"
fontWeight : string ; // "300"
lineHeight : string ; // "1.6"
opacity : string ; // "75"
fontFamily : string ; // "CodexMono, CodexMono EA"
}
Loading:
// From WikiBaseViewer
this . settings = {
fontSize: localStorage . getItem ( "wikiViewerFontSize" ) || "12" ,
fontWeight: localStorage . getItem ( "wikiViewerFontWeight" ) || "300" ,
// ... other settings with defaults
};
Settings apply to the entire viewer container via getViewerStyles().
State Comparison
OnIt has the simplest state model:
Viewer Own State Props Total State Complexity OnIt 1 7 Simplest Prepare 2 8 Low-Medium Prove 3 9 Medium Unified 4 10 Medium-High Docs 5 11 Highest
Minimal state aligns with SMPC: simplicity over complexity.
THE CENTER Simple state enables predictable behavior
Technical Credibility Single source of truth pattern. Unidirectional data flow. TypeScript type safety via interfaces. Automatic garbage collection for memory management. No manual listener cleanup required.