UI_RACK_LAYOUT.md (13377B)
1 # UI Rack Layout (draft) 2 3 This doc describes a planned UI architecture change: turning the Bzl desktop layout into a **rack of dockable panels**. 4 5 The goal is to make core views (Hives, Chat, People, Moderation, Maps, Library, etc.) and **plugins** behave the same way: 6 - They are panels you can move between columns (“racks”). 7 - They can be minimized into edge docks. 8 - They can be restored from a bottom hotbar of small “orbs”. 9 10 This is a **design + migration doc**, not a completed implementation yet. 11 12 ## Why (problem statement) 13 14 Today, plugins often: 15 - Directly query and manipulate core DOM nodes (`.main`, `.panelHeader`, etc.). 16 - Hide/show core panels with custom CSS. 17 - Invent their own navigation inside existing panes. 18 19 That works for MVP, but it creates friction: 20 - Plugins can fight with the core layout. 21 - Core layout changes become breaking changes for plugins. 22 - “Maps inside Hives” (or any plugin inside a core panel) makes it hard to treat the plugin as a first-class app surface. 23 24 The rack layout solves this by giving every surface a consistent home: **a panel container managed by core**. 25 26 ## Terms 27 28 ### Surface 29 A UI “screen” that renders content. Examples: Hives feed, a chat thread, the People list, the Maps room UI, a plugin directory. 30 31 ### Panel roles 32 Panels are dockable, but not all panels should behave the same way. We classify panels by *role*: 33 34 - **Primary**: the main workspace surfaces that want the most space (e.g. Hives, Chat, Maps, Directory). 35 - **Auxiliary**: useful alongside a primary surface, usually in a side stack (e.g. People, Moderation, Library). 36 - **Transient**: task flows that shouldn’t permanently occupy workspace real estate (e.g. Profile, New Hive composer, setup wizards). 37 - **Utility**: small tools that are frequently docked/minimized (logs, debug). 38 39 Rack mode should feel great out of the box by using these roles to drive defaults and constraints. 40 41 ### Display modes 42 Each panel should support different display modes (even if the first implementation only ships a subset): 43 44 - `full`: normal panel (default when active) 45 - `collapsed`: header-only (no body rendered) 46 - `overlay`: rendered as an overlay/drawer on top of a rack (ideal for transient panels) 47 48 The rack system uses these modes to prevent “everything is a full-height panel” layouts. 49 50 ### Panel 51 A container for one surface, with standard chrome: 52 - title / icon 53 - drag handle 54 - close / minimize / restore 55 - optional “pin” behavior 56 57 Panels are the atomic units of layout. 58 59 ### Rack 60 A column that holds an ordered list of panels. 61 62 Initial scope (MVP) assumes a few racks: 63 - left rack (current sidebar) 64 - main rack (current main content) 65 - right rack (current “People” / “Moderation” area) 66 67 Future scope could allow splits inside a rack (stacking panels vertically), but this doc does not require it. 68 69 ### Dock 70 An edge storage area for minimized panels: 71 - left dock 72 - right dock 73 - bottom hotbar (primary “closed panel” surface) 74 75 ### Orb 76 The minimized representation of a panel in the bottom hotbar (small pill/circle with label + optional badge). 77 78 Orbs can be: 79 - clicked to restore the panel 80 - dragged into a rack to restore at a specific position 81 82 ## UX goals 83 84 - Panels are **movable** between racks by drag-and-drop. 85 - Panels are **minimizable** into a dock (no content rendered; just an orb). 86 - The bottom hotbar **auto-hides** but appears on hover/near-bottom cursor. 87 - The layout is **per-user** and persisted (initially `localStorage`). 88 - Plugins can register panels without DOM poking. 89 90 ## Non-goals (for the first version) 91 92 - Perfect desktop-window-manager behavior (snapping, tearing out into new windows). 93 - Nested splits, arbitrary grids, or tiling layouts. 94 - Multi-user shared layouts. 95 - Heavy animation polish (we can add later). 96 97 ## Current layout (reference) 98 99 The current app is a CSS grid with: 100 - left sidebar 101 - main area 102 - optional right moderation panel 103 - resizers between these major columns 104 105 The rack model can be implemented *on top of* the current grid by treating each grid area as one rack. 106 107 ## Proposed layout state model 108 109 Persist as JSON in `localStorage` (per user) with a schema like: 110 111 ```json 112 { 113 "version": 1, 114 "presetId": "discordLike", 115 "racks": { 116 "left": ["instance", "hives"], 117 "main": ["chat"], 118 "right": ["people", "moderation"] 119 }, 120 "docked": { 121 "bottom": ["maps", "library"], 122 "left": [], 123 "right": [] 124 }, 125 "sizes": { 126 "leftWidth": 340, 127 "rightWidth": 360 128 } 129 } 130 ``` 131 132 Notes: 133 - Panel ids are stable strings (core + plugins share the namespace). 134 - A panel must exist in **exactly one** place: a rack or a dock. 135 - Unknown panels should be ignored safely (e.g. plugin removed). 136 - Missing panels can be auto-inserted into default racks (migration). 137 - `presetId` is informational (what preset the current layout originated from). 138 139 ## Panel identity and ownership 140 141 Panel ids are global (e.g. `"maps"`, `"library"`, `"people"`). 142 143 Every panel has: 144 - `id` (string) 145 - `title` (string) 146 - `icon` (optional) 147 - `source`: 148 - `"core"` (built-in) 149 - `"plugin:<pluginId>"` (plugin-owned) 150 - `role` (string): `primary` | `aux` | `transient` | `utility` 151 - `defaultRack` (string): `left` | `main` | `right` 152 153 ## Proposed plugin UI API (draft) 154 155 Today, a client plugin registers: 156 157 ```js 158 window.BzlPluginHost.register("maps", (ctx) => { /* ... */ }); 159 ``` 160 161 The rack layout introduces a panel-oriented extension: 162 163 ```js 164 ctx.ui.registerPanel({ 165 id: "maps", 166 title: "Maps", 167 icon: "🗺️", // optional (or use a CSS class / svg id) 168 defaultRack: "right", // "left" | "main" | "right" 169 role: "primary", // "primary" | "aux" | "transient" | "utility" (optional) 170 orderHint: 50, // optional: helps default ordering 171 render(mount, api) { 172 // mount is a DOM element owned by the panel container 173 // render into mount; return optional cleanup() 174 } 175 }); 176 ``` 177 178 Design constraints: 179 - Plugins should not need to query core DOM nodes to mount UI. 180 - Core can add consistent chrome (title bar, minimize button, etc.) without plugin changes. 181 - Core controls visibility and lifecycle: `render()` when shown, `cleanup()` when closed/uninstalled. 182 183 ### Panel `render()` contract (draft) 184 185 - Called when the panel becomes visible (restored into a rack). 186 - Receives: 187 - `mount`: a container element unique to that panel instance. 188 - `api`: helpers (toast, ws send, user/role, persistent panel storage). 189 - Should return: 190 - `() => void` cleanup function (remove event listeners, timers). 191 192 ### Minimal helper APIs a panel needs 193 194 Suggested `api` surface (exact shape TBD): 195 - `api.toast(title, body)` 196 - `api.send(eventName, payload)` (same as current `ctx.send`) 197 - `api.getUser()` / `api.getRole()` 198 - `api.storage.get(key)` / `api.storage.set(key, value)` (panel-scoped persistence) 199 - `api.requestAttention({ badge, pulse })` (optional: hotbar badge) 200 201 ## Migration plan for existing plugins 202 203 ### Phase 0 (now) 204 Plugins can manipulate the DOM directly. This is allowed but fragile. 205 206 ### Phase 1 (introduce panel containers) 207 Core exposes `ctx.ui.registerPanel()` and provides: 208 - a stable mount node for panel content 209 - a stable way to show/hide panels 210 211 During this phase: 212 - existing plugins can continue working 213 - updated plugins can opt into panels 214 215 ### Phase 2 (deprecate DOM poking) 216 Update docs and templates: 217 - “Do not query core DOM nodes” becomes the recommended path. 218 - `ctx.ui.registerPanel()` becomes the default expectation for UI plugins. 219 220 ## Core surfaces as panels 221 222 Core should register its own panels using the same mechanism (internal): 223 - `hives` (feed + filters + compose) 224 - `chat` (thread + composer) 225 - `people` 226 - `moderation` 227 - `instance` (settings/plugins) 228 229 This enables the “everything is a panel” mental model for users. 230 231 ## Separating Maps from Hives 232 233 Under the rack model, Maps becomes a standalone panel: 234 - Panel id: `maps` 235 - Default rack: `right` (or `main`, depending on preference) 236 - Behavior: 237 - Opening a map room switches the **Maps panel’s internal mode** (list vs room) without hijacking the Hives panel. 238 239 This avoids “Maps inside Hives”, which is hard to reason about and makes plugin upgrades brittle. 240 241 ## Bottom hotbar (“orbs”) 242 243 Behavior: 244 - Any panel can be minimized → moved to `docked.bottom`. 245 - Docked panels show as orbs with: 246 - title (short) 247 - optional icon 248 - optional badge (count, dot) 249 - Click orb → restore to its last rack (or default rack). 250 - Drag orb → hover racks to preview drop targets → drop to restore. 251 252 Persistence: 253 - dock state is per-user (localStorage) and survives reload. 254 255 ## Preset layouts (MVP) 256 257 We should ship a few **layout presets** so users can switch modes without hand-arranging panels every time. 258 259 ### Default presets (proposed) 260 261 Notes / constraints: 262 - Full-height columns only (no half-height / vertical splits). 263 - The workspace can show up to 2 primary slots at once (left + right). 264 - The right rack is a **single** skinny-capable panel (toggleable). 265 - Skinny-capable panels: `people`, `profile`, `composer`, `hives` (list), `chat`. 266 - Moderation panels only exist for moderators (mod-only presets live in their own section). 267 268 #### Non-mod presets 269 270 1. **Default (Social)** 271 - Workspace: `hives` | `chat` 272 - Right rack (skinny): `people` 273 - Side (collapsed): `profile`, `composer` 274 - Docked bottom: `maps`, `library` (if installed) 275 276 2. **Chat Focus** 277 - Workspace: `chat` (expanded) 278 - Right rack (skinny): `people` 279 - Side (collapsed): `profile` 280 - Docked bottom: `hives`, `composer`, `maps`, `library` (if installed) 281 282 3. **Browse** 283 - Workspace: `hives` (expanded) 284 - Right rack (skinny): `profile` (inspector) 285 - Side (collapsed): `chat` 286 - Docked bottom: `people`, `composer`, `maps`, `library` (if installed) 287 288 4. **Creator** 289 - Workspace: `hives` | `composer` 290 - Right rack (skinny): `profile` 291 - Side (collapsed): `people` 292 - Docked bottom: `chat`, `maps`, `library` (if installed) 293 294 5. **Maps Session** 295 - Workspace: `maps` | `chat` 296 - Right rack (skinny): `people` 297 - Side (collapsed): `hives` (list) 298 - Docked bottom: `profile`, `composer`, `library` (if installed) 299 300 6. **Quiet (No People)** 301 - Workspace: `hives` | `profile` 302 - Right rack (skinny): (empty / off) 303 - Side (collapsed): `composer` 304 - Docked bottom: `chat`, `people`, `maps`, `library` (if installed) 305 306 #### Mod-only presets 307 308 7. **Ops** 309 - Workspace: `moderation` | `chat` 310 - Right rack (skinny): `people` 311 - Side (collapsed): `hives` (list) 312 - Docked bottom: `profile`, `composer`, `maps`, `library` (if installed) 313 314 8. **Reports Focus** 315 - Workspace: `moderation` (expanded) 316 - Right rack (skinny): `chat` 317 - Side (collapsed): `people` 318 - Docked bottom: `hives`, `profile`, `composer`, `maps`, `library` (if installed) 319 320 9. **Community Watch** 321 - Workspace: `hives` | `moderation` 322 - Right rack (skinny): `people` 323 - Side (collapsed): `chat` 324 - Docked bottom: `profile`, `composer`, `maps`, `library` (if installed) 325 326 10. **Server Admin** 327 - Workspace: `moderation` | `hives` 328 - Right rack (skinny): `people` 329 - Side (collapsed): `chat` 330 - Docked bottom: `profile`, `composer`, `maps`, `library` (if installed) 331 332 These are intentionally opinionated. Users can always customize after selecting a preset. 333 334 ### How presets apply 335 336 Selecting a preset: 337 - **Hard apply**: replaces the current rack/dock assignments with the preset template (exact placement). 338 - Any panel not explicitly placed by the preset starts in the hotbar. 339 - Moderation panels are omitted for non-moderators; mod-only presets should either be hidden or gracefully degrade (e.g. replace `moderation` with `people`). 340 - Optionally preserves column widths (or resets them; TBD). 341 - Saves the result as the user’s current layout. 342 343 ### Plugins extending presets (draft) 344 345 Plugins may register a panel and optionally provide **preset hints**, e.g.: 346 - preferred default rack for each preset 347 - whether the panel should start docked for a preset 348 349 Example shape (not final): 350 ```js 351 ctx.ui.registerPanel({ 352 id: "maps", 353 title: "Maps", 354 defaultRack: "right", 355 presetHints: { 356 discordLike: { place: "docked.bottom" }, 357 browsing: { place: "right" }, 358 moderation: { place: "docked.bottom" }, 359 chat: { place: "docked.bottom" } 360 } 361 }); 362 ``` 363 364 Core should treat these hints as *suggestions*: 365 - If a preset doesn’t know a plugin panel, it can still place it using `defaultRack` (or dock it). 366 - If a plugin isn’t installed, the preset remains valid. 367 368 ## Notes for plugin authors 369 370 When the rack layout exists: 371 - Prefer `ctx.ui.registerPanel()` for all UI. 372 - Avoid selecting core elements like: 373 - `.main`, `.panelFill`, `.panelHeader`, `#feed`, etc. 374 - Treat your plugin UI as “just a panel”: 375 - it should render into its mount 376 - it should not assume what other panels exist or where they are 377 378 ## Open questions 379 380 - Should panels be “single-instance” only, or can a plugin open multiple panels (e.g. multiple map rooms)? 381 - Should panel layout persist per-device or per-user (server-side)? 382 - How do we expose keyboard shortcuts for focusing panels? 383 - What is the best “default” panel set for first-time users? 384 385 ## Mobile (separate model) 386 387 Rack layout is desktop-first. On mobile, we should treat panels as **screens + tools** (single active surface with a bottom nav + “More” sheet), while keeping the same panel registration model underneath. 388 389 See `docs/MOBILE_UX.md`.