bzl

self-hosted ephemeral community engine
Log | Files | Refs | README | LICENSE

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`.