Monorepo for Aesthetic.Computer
aesthetic.computer
1# Presence System Report
2
3## Current Architecture Overview
4
5There are **TWO separate WebSocket connection systems** that track users:
6
7### 1. Main Session Server WebSocket (port 8889)
8- **File**: `session.mjs`
9- **Connects**: On initial page load, stays connected across ALL pieces
10- **Tracks in `clients[]`**:
11 - `handle` - user's @handle
12 - `user` - auth0 sub ID
13 - `location` - current piece slug (via `location:broadcast` messages)
14 - `ip`, `geo`, etc.
15- **Also tracks `worldClients[piece][id]`**:
16 - Only for pieces with "world" features (like `field`)
17 - Uses `world:*:join`, `world:*:leave`, `world:*:move` messages
18 - Handles ghosting/unghosting when users navigate away and back
19
20### 2. Chat WebSocket (separate connections per instance)
21- **File**: `chat-manager.mjs`
22- **Instances**: `chat-system`, `chat-sotce`, `chat-clock`
23- **Connects**: From `lib/chat.mjs` when a piece calls `chat.connect("system")`
24- **Tracks per instance**:
25 - `instance.connections[id]` - WebSocket connections (ALL connected sockets)
26 - `instance.authorizedConnections[id]` - Only authenticated users who have sent a message
27 - `instance.authorizedConnections[id].handle` - Their @handle
28
29---
30
31## Why the Online Counter is Inaccurate
32
33### Problem 1: Chat connects globally, not per-piece
34The chat connection is established from `lib/chat.mjs` and **persists across piece navigation**. When you're on `aesthetic.computer/prompt` or any other piece, you're still connected to chat-system. The `chatterCount` in chat shows ALL connected WebSockets, not just users actually viewing the `chat` piece.
35
36### Problem 2: `onlineHandles` only shows AUTHORIZED users
37The `getOnlineHandles()` function in chat-manager.mjs:
38```javascript
39getOnlineHandles(instance) {
40 const handles = [];
41 for (const [id, auth] of Object.entries(instance.authorizedConnections)) {
42 if (auth.handle && instance.connections[id]) {
43 handles.push(auth.handle);
44 }
45 }
46 return [...new Set(handles)];
47}
48```
49This only includes users who:
501. Have an open WebSocket connection
512. Have successfully authorized (sent a chat message with valid token)
523. Have a handle stored
53
54**Anonymous viewers or logged-in users who haven't chatted** are NOT in `onlineHandles`.
55
56### Problem 3: Connection count includes duplicates
57`Object.keys(instance.connections).length` counts WebSocket connections, not unique users. One user with multiple tabs = multiple connections.
58
59---
60
61## What We Know About User Location
62
63The **main session server** (`session.mjs`) DOES track which piece users are on:
64
65```javascript
66// From location:broadcast handler (line 1708-1750)
67if (msg.content.slug !== "*keep-alive*") {
68 clients[id].location = msg.content.slug;
69 log(`📍 Location updated for ${clients[id].handle || id}: "${msg.content.slug}"`);
70}
71```
72
73This is published to Redis:
74```javascript
75pub.publish("slug:" + msg.content.handle, msg.content.slug)
76```
77
78**Key insight**: The session server knows exactly which piece each user is on!
79
80---
81
82## Proposed Solution: "Here" Counter for Chat Piece
83
84### Option A: Cross-reference session server location data
85
86The chat-manager could query the session server's `clients[]` to see which authenticated chat users have `location === "chat"`.
87
88**Implementation**:
891. Export a function from session.mjs: `getClientsOnPiece(piece)`
902. In chat-manager, after broadcasting `online-handles`, also send `here-handles`
913. `here-handles` = intersection of `onlineHandles` AND users where `clients[id].location === "chat"`
92
93### Option B: Chat piece explicitly notifies "in-view"
94
95When the `chat` piece mounts, it could send a message like `chat:enter`. When unmounting/leaving, `chat:leave`.
96
97**Pros**: Explicit, accurate
98**Cons**: Requires piece-level code changes, needs cleanup on disconnect
99
100### Option C: Track "last seen piece" per chat connection
101
102Map chat connection IDs to session server connection IDs (via handle or token), then look up their `location`.
103
104---
105
106## Data Flow Diagram
107
108```
109┌──────────────────────────────────────────────────────────────┐
110│ BROWSER │
111│ ┌─────────────────────────────────────────────────────────┐ │
112│ │ BIOS/Disk │ │
113│ │ - Connects to session-server:8889 on load │ │
114│ │ - Sends location:broadcast when piece changes │ │
115│ │ - Stays connected across ALL pieces │ │
116│ └─────────────────────────────────────────────────────────┘ │
117│ │ │
118│ ┌───────────────────────────┼──────────────────────────────┐│
119│ │ lib/chat.mjs │ ││
120│ │ - Called by chat piece │ ││
121│ │ - Connects to chat-*.aesthetic.computer ││
122│ │ - Also persists across piece navigation! ││
123│ └───────────────────────────┼──────────────────────────────┘│
124└──────────────────────────────┼───────────────────────────────┘
125 │
126 ┌─────────────────────┴─────────────────────┐
127 ▼ ▼
128┌─────────────────────┐ ┌─────────────────────┐
129│ session.mjs:8889 │ │ chat-manager.mjs │
130│ │ │ (chat-*.aesthetic) │
131├─────────────────────┤ ├─────────────────────┤
132│ clients[id] = { │ │ connections[id] = │
133│ handle, │ │ WebSocket │
134│ user, │ ← NO LINK → │ │
135│ location: "chat" │ │ authorizedConns = │
136│ } │ │ { token, handle } │
137├─────────────────────┤ ├─────────────────────┤
138│ KNOWS: who's on │ │ KNOWS: who's │
139│ "chat" piece │ │ connected │
140│ │ │ to chat WS │
141│ DOESN'T KNOW: │ │ │
142│ who's auth'd for │ │ DOESN'T KNOW: │
143│ chat specifically │ │ actual piece │
144└─────────────────────┘ └─────────────────────┘
145```
146
147---
148
149## Recommended Implementation
150
151### Phase 1: Bridge the data (quick win)
152
153In `session.mjs`, add a function:
154```javascript
155function getHandlesOnPiece(pieceName) {
156 return Object.values(clients)
157 .filter(c => c.location === pieceName && c.handle)
158 .map(c => c.handle);
159}
160```
161
162Export this so chat-manager can call it, or expose via internal API.
163
164### Phase 2: Broadcast "here" in chat
165
166Modify `broadcastOnlineHandles()` in chat-manager:
167```javascript
168broadcastOnlineHandles(instance) {
169 const handles = this.getOnlineHandles(instance);
170 const hereHandles = this.getHereHandles(instance); // Users actually on chat piece
171 this.broadcast(instance, this.pack("presence", {
172 online: handles, // All auth'd chat connections
173 here: hereHandles // Only those on "chat" piece right now
174 }));
175}
176```
177
178### Phase 3: UI in chat.mjs
179
180```javascript
181// Currently cycles through onlineHandles
182// Add a "Here" section that shows users actually viewing chat
183const hereHandles = client?.hereHandles || [];
184const onlineHandles = client?.onlineHandles || [];
185```
186
187---
188
189## Questions to Resolve
190
1911. **Should chat disconnect when leaving the chat piece?**
192 - Currently it stays connected (enables notifications anywhere)
193 - Could add explicit `chat:enter`/`chat:leave` events instead
194
1952. **How to link session ID to chat ID?**
196 - Both have `handle` - match on that?
197 - Or pass session ID in chat auth?
198
1993. **Should "here" count anonymous viewers?**
200 - Session server tracks location for everyone
201 - Chat only tracks authorized users
202
203---
204
205## Files to Modify
206
2071. `session-server/session.mjs` - Export helper function or add internal API
2082. `session-server/chat-manager.mjs` - Add "here" calculation and broadcast
2093. `system/public/aesthetic.computer/lib/chat.mjs` - Handle new `presence` message type
2104. `system/public/aesthetic.computer/disks/chat.mjs` - Display "here" users
211
212---
213
214*Report generated: 2026-01-17*