GENERATED VIEW · PRIVATE ARCHIVE · DO NOT SERVE
RobCo Industries · Archive Museum

PHASE3_OPERATIONS_PLAN.md



RELEASE

planning/2.8.0/plans/PHASE3_OPERATIONS_PLAN.md

sha256 0842e415528f1261 · 30659 bytes · original held in the private archive

# Phase 3 · Piece 2 — DRESS the OPERATIONS / INVENTORY screen into the NV "machine" language **Branch:** `dev` · **HEAD:** `f0f54a9` · **APP_VERSION:** `2.7.0` (`[Unreleased]`) **Flow:** plan → **Fable mockup** → owner approve → build (same as OPERATOR / Settings-tab / campaign-configs). **Scope:** PLAN ONLY. No code edits, no commits, no gate. Implementation + the mockup are later passes. **Bezel target:** `[2] OPERATIONS · INV` (`data-tab="inv"`). --- ## 0. Where OPERATIONS is today — this is NOT a head start Unlike OPERATOR (which the NV machine mockup already sketched end-to-end), **OPERATIONS has never been dressed.** `nv-machine-rationale.md` §"Honest scope" explicitly left OPERATIONS/DATABANK/CHASSIS rendering as a placeholder "SUBSYSTEM ONLINE" teaser. So this is a **from-scratch dressing** of the densest subsystem in the app — and that freedom is exactly why the owner wants the ambition pushed further here than the big three. **The machine language is already fully built in CSS** (shipped with OPERATOR / Module Bay), so this is dressing, not invention. Reusable, in `css/terminal.css` today: - `.panel.bay-board` / `details.bay-board` — the board frame (+ `::after` copper connector strip via `background-repeat: round`, Suite 160 stray-pin rule) - `.bay-slot-tag` (top-right `BUS-0N` chip) · `.bay-part-no` (`PN RBC-… · SUB-LABEL` plain-English ride-along, Protocol 25 guardrail) - `.board-led` (summary status lamp) · `.panel-substatus` (the **collapsed one-line status row**, the 0i standard) - `.bay-board.span2` (two-up on desktop) · `.uplink` / `.tray` / `.dangerboard` accent variants OPERATOR already consumes all of these (see `index.html` L1397 SKILL MATRIX: `bay-board` + `board-led` + `panel-substatus#opSkillsStatus` + `bay-slot-tag BUS-05` + `bay-part-no`). **OPERATIONS reuses the identical classes** — that is what makes the whole app read as one device. --- ## 1. The consistency floor (what OPERATIONS must match, then exceed) Owner directive: the merged **Comm-Link (DO-O Director Uplink oscilloscope) + SETTINGS tab (Module Bay + campaign-config boards)**, now joined by the dressed **OPERATOR**, is the LOWEST floor. OPERATIONS must read as the **same machine** and push further. Shared vocabulary it must honor: - **Amber-on-phosphor palette:** green (`#14fdce`) = **local/owned** (cargo you carry, your weight, your caps). **Amber (`--bezel-wire`)** = **"over the wire"** / remote — so **BARTER UPLINK leans amber** (the vendor is a networked/remote party), matching the Director Uplink's amber. Hazards (over-encumbered, insufficient caps, vendor-can't-afford) use `--robco-danger` red (the INTERLOCK precedent). - **Collapsible board DNA:** every board is a `<details class="panel bay-board">` whose **closed state is a one-line LED status row** (`.panel-substatus`, 0i — never information-free). OPERATIONS already IS a stack of `<details>` panels — natural fit, and open/closed persistence (`robco_panel_state`) **plus per-subsystem scroll restore (`robco_scroll_positions` / `_restoreScrollFor`) come for free** (owner's "remembers open/closed + scroll" requirement is already satisfied by keeping the `<details>` structure + `data-sub-id`s). - **Slot/board fiction:** each block carries a diegetic `BUS-NN` id + a real plain-English sub-label. OPERATIONS's fiction = a **quartermaster / freight-logistics bus** (continue the OPERATOR bus numbering: OPERATOR used BUS-01…09; **OPERATIONS = BUS-10…15** so the whole device shares one part-number series). - **Copper edge-connectors** via `background-repeat: round` (already the `.bay-board::after` default). - **All new motion is plain `animation`** (never `transition:` alone) → auto-neutralized by the existing global `prefers-reduced-motion` block, no bespoke carve-out. Named verbs available: SWEEP (nav), SEAT (seat/overshoot). OPERATIONS adds DRAWER-SLIDE + WEIGH-NEEDLE-SEAT + over-encumber SHUDDER as plain animations. - **Centering rule:** incomplete flex rows (drawer keycaps, squad affinity buttons, filter chips) center under `flex-wrap + justify-content: center` (Suite 159 precedent). - **Per-game via `GAME_DEFS[ctx]`** (Protocol 38) — see §5. **The 1-of-1 escalation (see §7).** OPERATOR's instrument is *biometric* (gauges, needles, body-scan). OPERATIONS's instrument is *physical freight*: a **load-cell weigh bridge** + a **pull-drawer cargo bank**. Same machine, different organ. --- ## 2. Element-by-element dressing spec (the meat) Below is **EVERY** control/readout actually on `data-tab="inv"` today (verified against `index.html` @ `f0f54a9` and the render fns in `js/ui-render.js`). Five panels today: **BACKPACK INVENTORY** (L1167), **COLLECTIBLES** (L1316), **CRAFTING** (`#craftPanel` L1344), **BARTER UPLINK** (`#tradePanel` L1362), **SQUAD STATUS** (L1613). The dressed form is a **visual wrapper around the exact same element ids** — see §3. > Proposed board order top→bottom (mobile): **WEIGH BRIDGE hero** (new, always-open) → **CARGO MANIFEST** (open) → **FIELD FABRICATION** (0i) → **BARTER UPLINK** (0i) → **SQUAD ROSTER** (0i) → **CURIO ARCHIVE** (0i). ### Board 0 — LOAD-CELL WEIGH BRIDGE *(NEW hero — read-only mirror, no new state)* ⭐ hero #1 Fiction: `BUS-10 · LOAD-CELL WEIGH BRIDGE`. The glanceable anchor, the way VITAL TELEMETRY anchors OPERATOR. Always-open, no collapse. | Element | Source | Dressed form | Stays functional | | --- | --- | --- | --- | | Carry weight (`display_weight`, `curWt / maxWeight`) | computed in `updateMath()` (ui-core L5249–5258) | **Physical strain-gauge / analog scale** — needle sweeps to load %, with `weight-heavy` (≥75%) amber, `weight-critical` (≥90%), and **over-encumbered "SEIZED"** red state at ≥100% (the `weight-over`/`weight-heavy`/`weight-critical` body classes already exist — the gauge reads them, never sets them) | ✅ **read-only mirror.** Source of truth stays the OPERATOR `display_weight` field + `updateMath()`. OPERATIONS renders a *second projection* only (mirror element, e.g. `#opsWeighBridge`, painted by a small hook inside `updateMath()` right where `display_weight` is set). Zero new state, zero handler fork. | | Caps (`c_caps` / `state.caps`) | `updateMath()` | **Requisition-credit odometer** readout (mirror) | ✅ read-only mirror; `c_caps` on OPERATOR stays the editable source. | **Why this is safe (no-regression):** the weigh bridge **reads** `curWt`/`maxWeight`/`caps` that `updateMath()` already computes; it never writes state and never relocates the editable fields (they stay on OPERATOR). This is the "second projection through one apply path" pattern (Module Bay bay↔schematic precedent) — the OPERATOR number and the OPERATIONS gauge can never disagree because both are painted by the one `updateMath()` pass. **Open Q2** covers whether owner wants this at all. ### Board 1 — CARGO MANIFEST *(BACKPACK INVENTORY, L1167–1313)* ⭐ hero #2 — solves the mobile problem Fiction: `BUS-11 · CARGO MANIFEST`. Default-open. **This board is where the no-infinite-scroll fight is won (§4).** | Element (id) | Handler | Dressed form | Stays functional | | --- | --- | --- | --- | | Filter bar (`#invFilterBar` — 7 buttons `data-filter` all/weapon/armor/aid/mod/misc/ammo) | `onclick=setInvFilter(cat)`, active-class toggle | **Pull-drawer bank** — one labeled keycap-drawer per category (WEAPONS/APPAREL/AID/MODS/MISC/AMMO), **exactly one open at a time**, each with a live **count badge**. This IS the filter (already single-select), just physicalized. `MODS` drawer hidden per game (below). | ✅ same `setInvFilter()` / `_invFilter`; drawer taps call it verbatim. Muscle memory preserved (still single-select). | | `#invFilterMods` | gated by `hasWeaponMods` in `_updateContextPanels()` | MODS drawer shown/hidden by the **existing** `_updateContextPanels()` display toggle (FO3 has no mods) | ✅ unchanged gating. | | Item list (`#invList` `<ul>`) | `renderInventory()` + click-delegation (`data-idx`→`delItem`, `data-use`→USE) | **Stamped manifest line-items** — each row a requisition card: `[USE]` `[TYPE]` `qty× NAME (wt · valc)` `[X]`. Only the **open drawer's** items render (`renderInventory()` already filters by `_invFilter`). | ✅ same `renderInventory()` markup contract (`data-use`/`data-idx` ids/handlers unchanged); USE still fires `> [USE] name` → `transmitMessage()`; X still `delItem()`. | | Add-item form (`#newItemName` [registry autocomplete `items`], `#newItemQty`, `#newItemWeight`, `#newItemValue`, `#newItemType`) | `onclick=addItem()` (DB auto-populate + ammo-routing) | **"REQUISITION SLIP" input row** at the board foot | ✅ every id/attr kept; `addItem()` unchanged (incl. `lookupItemInDb` auto-fill + `type==='ammo'` → `state.ammo` routing); `newItemName` autocomplete (`wireInput('newItemName','items')`) preserved. `maxlength` caps preserved (Protocol 54). | | AMMO RESERVES sub-panel (`#ammoSubPanel` `data-sub-id=ammo_reserves`, `#ammoList`, add-ammo `#newAmmoType` [datalist `#ammoCalibers`] + `#newAmmoCount` → `addAmmo()`) | `renderAmmo()`, `addAmmo()`, `removeAmmo()` | The **AMMO drawer** (see Open Q9): selecting the AMMO drawer surfaces `#ammoList` (caliber/count/X grid) + the add-ammo row, **in place of** the item list. Fold visually into the drawer bank while **keeping `#ammoSubPanel`/`#ammoList`/`#newAmmoType`/`#newAmmoCount` ids intact**. | ✅ `renderAmmo()`/`addAmmo()`/`removeAmmo()` untouched; datalist autocomplete kept. | **Optional value-add (Open Q6):** per-row **quantity ± steppers**. Today qty only changes via re-add(increment)/`delItem`(remove-all)/USE/craft/trade — there is **no decrement-by-one**. Adding `[-] qty [+]` per manifest row is a genuine UX win and "bigger ambition," but it introduces a new write path (needs a tiny `adjItemQty(idx, delta)` helper + regression test, Protocol 13). Recommend as a stretch, defaulting to the safe current behavior if scope tightens. ### Board 2 — FIELD FABRICATION *(CRAFTING, `#craftPanel` L1344–1359)* ⭐ hero #3 Fiction: `BUS-12 · FIELD FABRICATION`. Default 0i-collapsed on mobile. | Element (id) | Handler | Dressed form | Stays functional | | --- | --- | --- | --- | | Recipe picker (`#craftRecipeList` → `#craftRecipeSelect` optgroups-by-station + `#craftRecipeCard`) | `renderCraft()` / `renderCraftCard()`; `craftQty_<idx>`; `doCraft(idx)` confirm-gated | **Fabrication bench**: station-grouped recipe selector + a **recipe card with per-ingredient HAVE/NEED meters** (green when satisfied, red when short — data already in `_craftPrepare().missing`), qty stepper, a **"FABRICATE" board key** | ✅ `renderCraft`/`renderCraftCard` markup contract kept (still emits `#craftRecipeSelect`/`#craftRecipeCard`/`craftQty_<idx>`); `doCraft()` confirm gate (`confirmAction`) + `_craftApply` mutation core unchanged. | | Scrap sub-panel (`data-sub-id=craft_breakdown`, `#craftScrapList` → `#scrapItemSelect` + `#scrapItemCard`) | `renderScrapCard()`; `scrapQty_<idx>`; `doScrap(idx)` confirm-gated | **"BREAKDOWN VISE" sub-board** — item selector + yield card | ✅ ids/handlers kept; `doScrap()`/`_scrapApply` unchanged. | ### Board 3 — BARTER UPLINK *(TRADE, `#tradePanel` L1362–1394)* — amber "over the wire" Fiction: `BUS-13 · BARTER UPLINK`. Default 0i-collapsed on mobile. **Amber-accented** (remote vendor). | Element (id) | Handler | Dressed form | Stays functional | | --- | --- | --- | --- | | Vendor header (`#tradeHeader` → `#tradeVendorSelect`, `#tradeStats` purse line) | `renderTrade()`, `setTradeVendor(idx)`, `_renderTradeStats()` | **Vendor-link header** with a callsign readout + **purse gauge** (vendor caps) | ✅ `renderTrade` markup kept (`#tradeVendorSelect`/`#tradeStats`); `setTradeVendor()` unchanged. | | VENDOR STOCK (`data-sub-id=trade_buy`, `#tradeBuySearch` → `renderTradeBuyList()`, `#tradeBuyList` BUY→`doBuy`) | `renderTradeBuyList()`; `doBuy(name)` confirm-gated | **Buy-side balance rows** — name / price / BUY, dimmed when unaffordable (existing `afford` opacity logic). **Already render-capped at 40 with "+N more — refine search"** (the pattern OPERATIONS reuses everywhere). | ✅ `#tradeBuySearch`/`#tradeBuyList` ids + `renderTradeBuyList` kept; `doBuy()` (confirm + `c_caps` mirror fix) unchanged. | | COURIER INVENTORY (`data-sub-id=trade_sell`, `#tradeSellList` SELL→`doSell`) | `renderTradeSellList()`; `doSell(name)` confirm-gated | **Sell-side balance rows** — dimmed when vendor purse short | ✅ `#tradeSellList` + `renderTradeSellList` kept; `doSell()` unchanged. | ### Board 4 — SQUAD ROSTER *(SQUAD STATUS, L1613–1645)* Fiction: `BUS-14 · SQUAD ROSTER`. Default 0i-collapsed on mobile. | Element (id) | Handler | Dressed form | Stays functional | | --- | --- | --- | --- | | Companion list (`#squadList`) | `renderSquad()`; affinity `adjustAffinity(i,±5)`; `removeSquadMember(i)` | **Roster cards** — HP bar + HP/AMMO/CND row + WPN/DT + **affinity meter with `[+]`/`[-]` nudge** | ✅ `renderSquad()` markup kept; `adjustAffinity`/`removeSquadMember` unchanged. | | Add-companion form (`#newSquadName` `<select>` + `addSquadMember()`) | `onclick=addSquadMember()` | **"ENLIST" input row** | ✅ `#newSquadName`/`addSquadMember()` kept. **⚠ Protocol-38 flag:** the `<select>` **hardcodes 8 FNV companion names** in `index.html` — a game literal. `FALLOUT_REGISTRY.companions` exists in BOTH `reg_nv.js` and `reg_fo3.js`; the dressing should **populate the select from the registry** (Open Q7) so FO3 shows FO3 companions. | ### Board 5 — CURIO ARCHIVE *(COLLECTIBLES, L1316–1341)* Fiction: `BUS-15 · CURIO ARCHIVE`. Default 0i-collapsed on mobile. Game-agnostic label. | Element (id) | Handler | Dressed form | Stays functional | | --- | --- | --- | --- | | Collectibles sub-panel (`#collectiblesSubPanel`, `#collectiblesDisplay`) | `renderCollectibles()`; `toggleCollectible(name)` | **Curio slots** — `[ACQUIRED]`/`[MISSING]` tracker toggles, acquired-first split (already done), game-specific label (`collectibleLabel`: SNOW GLOBES/BOBBLEHEADS) + `[n/total]` count | ✅ `renderCollectibles()` markup + `toggleCollectible` kept; `.tracker-row`/`.tracker-toggle` contract (Suite 88) preserved. | | Lincoln sub-panel (`#lincolnSubPanel`, FO3-only `display:none`, `#lincolnMemorabiliaDisplay`) | `renderLincolnMemorabilia()` | Nested FO3 sub-board, restyled | ✅ id + FO3 gating (`tracksLincoln`) unchanged. | ### Related surfaces that are NOT OPERATIONS boards (documented, not dressed here) - **LOOT** (`renderLoot`, ui-render L2320) — a **modal** launched from the Tool Deck (uses `#sysModal` + `openModal`), not a persistent OPERATIONS panel. Adds to inventory. Out of scope for board dressing; the CARGO MANIFEST must simply reflect its adds (it does, via `renderInventory()`). - **EQUIPPED loadout** (`#equippedDisplay`, `renderEquipped`) — lives on **OPERATOR** (stat tab), and is **AI-write-only today** (the U10 native-input audit documented `state.equipped` has *no native setter*). See **Open Q3** — the owner listed "equip/unequip" as must-stay; it does **not exist as an interactive control today**, so surfacing/adding it is an *addition*, not a preserve. Big scope call. - **Weight/caps/AP source fields** (`display_weight`/`display_ap`/`c_caps`) — live on OPERATOR. AP is combat, not cargo → stays on OPERATOR. Weight+caps mirror into the WEIGH BRIDGE (Board 0, read-only). --- ## 3. The load-bearing constraint — the id-preservation contract (Protocol 22) `loadUI()` (ui-core L4838+) and `updateMath()` do direct `document.getElementById()` / dirty-checked `render*()` calls on a fixed id set every render — if any id disappears, boot throws or a panel silently stops updating. The dressing **must keep every one of these ids** (wrap/restyle around them, never rename): `invFilterBar invFilterMods invList` · `ammoSubPanel ammoList newAmmoType ammoCalibers newAmmoCount` · `newItemName newItemQty newItemWeight newItemValue newItemType` · `craftPanel craftRecipeList craftRecipeSelect craftRecipeCard craftScrapList scrapItemSelect scrapItemCard craftQty_<i> scrapQty_<i>` · `tradePanel tradeHeader tradeVendorSelect tradeStats tradeBuySearch tradeBuyList tradeSellList` · `squadList newSquadName` · `collectiblesSubPanel collectiblesDisplay lincolnSubPanel lincolnMemorabiliaDisplay`. Handlers stay too: `setInvFilter · addItem · delItem · renderInventory(USE-delegation) · addAmmo · removeAmmo · renderAmmo · doCraft · doScrap · renderCraft · renderCraftCard · renderScrapCard · setTradeVendor · doBuy · doSell · renderTrade · renderTradeBuyList · renderTradeSellList · renderSquad · addSquadMember · removeSquadMember · adjustAffinity · renderCollectibles · toggleCollectible · renderLincolnMemorabilia · _updateContextPanels(mods gate)`. Where a render fn emits markup (`renderInventory`/`renderAmmo`/`renderCraft`/`renderTrade*`/`renderSquad`/`renderCollectibles`), the drawer/card/gauge skin is a **controlled edit to that function's template**, still emitting the same ids/handlers. **No handler forked; no state field changed.** Keeping the boards as `<details class="panel bay-board">` preserves `robco_panel_state` open/closed persistence AND `robco_scroll_positions` scroll restore for free. (Module Bay / campaign-config reskin precedent, Protocol 25.) --- ## 4. Mobile approach — the no-infinite-scroll problem (the hard part) OPERATIONS is the densest subsystem: inventory (dozens of items) + ammo + a 20-entry collectible list + a full vendor catalog + squad. Stacked naïvely it's a mile of rows. **Four structural mechanisms bound it:** 1. **Short board stack (0i collapse).** Default-open **only the two heroes** (WEIGH BRIDGE + CARGO MANIFEST); every other board (FIELD FABRICATION / BARTER UPLINK / SQUAD ROSTER / CURIO ARCHIVE) opens **collapsed to its one-line `.panel-substatus` LED row**. The phone opens to a short, scannable stack (settings-tab / module-bay 0i precedent). Persisted open/closed is remembered. - 0i status lines (live): `CARGO · 37 ITEMS · WEAPONS drawer` · `FAB · 25 RECIPES · 12 BREAKDOWNS` · `BARTER · CHET — GOODSPRINGS · PURSE 300c` · `SQUAD · 2 ACTIVE · AVG AFF 60%` · `CURIOS · 7/20 SNOW GLOBES` · weigh bridge (always-open) header `CARGO 42.5 / 210 LB · 1,204c`. 2. **The drawer bank (the key insight).** The category filter is **already single-select** — so the CARGO MANIFEST only ever renders **one** category's items at a time. Physicalizing it as a **pull-drawer bank (one drawer open at a time, per-drawer count badge)** makes that boundedness *visible and intentional* instead of incidental. AMMO becomes its own drawer. You never see weapons + apparel + aid + misc stacked together. 3. **In-drawer search + render cap.** Add a compact **search field inside the manifest** (filters the open drawer) and a **render cap of N (recommend 50) with a "+N more — refine search" footer** — the exact pattern BARTER's `#tradeBuyList` already uses (cap 40). This bounds the worst case (a single huge category) to a fixed-height list; search reaches the rest. This is the one **new mechanic** to confirm (Open Q4/Q5). 4. **Bounded-by-construction sub-lists.** BARTER buy/sell already capped (40). Crafting uses `<select>`s (bounded). CURIO ARCHIVE is a collapsed sub-panel by default. Squad is naturally short. **Baseline invariants (Protocol 10/17):** - Pure-CSS/SVG gauges (weigh needle), sized in relative units; drawer keycap row + squad affinity buttons + filter chips **wrap and center** (incomplete last row centers). Zero horizontal overflow at 360 (`scrollWidth === innerWidth`). - **Tap targets ≥28px** (drawer keycaps, USE/X, BUY/SELL, ± steppers, board summaries 44px); **inputs ≥16px** (search, add-item/add-ammo fields, qty, vendor select). The kept number inputs must not shrink below 16px — dressing adds chrome, never miniaturizes editable fields. - **Reduced-motion:** drawer-slide, weigh-needle-SEAT, over-encumber SHUDDER, HAVE/NEED meter fills all as plain `animation` → global `prefers-reduced-motion` block collapses each to its resting frame automatically. - **Desktop (≥1000px):** boards sit two-up under the glass (`.bay-board.span2`) with the permanent Director column on the right (every subsystem). No new grid — reuse the shell. Desktop-only tweaks must carry the `(hover:hover) and (pointer:fine)` gate (Suite 129 — no bare `min-width` query). - **PWA intact:** pure markup/CSS/SVG + existing handlers over the working shell; no SW/scope change, no new document. A cache bump (Protocol 1) on the eventual build commit is the only `sw.js` touch. --- ## 5. Game-agnostic / data-driven story (Protocol 38) The dressing must generalize to FO3/FO4 via data, no feature-code game branches: - **Palette/flavor** ride the existing `--bezel-wire` `[data-game]` token (green-local vs amber-wire is NV's read; FO3 authors its own). No `ctx === 'FNV'` color branch. - **Drawer categories** are fixed item types (`weapon/armor/aid/mod/misc/ammo`); the **MODS drawer is hidden per `GAME_DEFS[ctx].hasWeaponMods`** — already gated by `_updateContextPanels()` (FO3 has no mods). Reset-off-the-mod-filter fail-safe already present. - **CURIO ARCHIVE label** from `_activeDef().collectibleLabel` (SNOW GLOBES / BOBBLEHEADS); Lincoln sub-panel per `tracksLincoln`. Already data-driven. - **Vendors / catalog / prices** from `getVendors()` / `getTradeCatalog()` / `GAME_DEFS[ctx].barter` + `state.skills.barter`. Recipes/breakdowns from `FALLOUT_REGISTRY.recipes`/`.breakdowns` (empty ⇒ "No recipes for this game" already handled). - **Squad enlist select** → drive from `FALLOUT_REGISTRY.companions` (fixes the current FNV hardcode — Open Q7). - Any per-game **board titles / BUS labels / weigh-bridge or drawer captions** needing flavor go on a new **`identity.operations`** sub-block (mirroring `identity.overseer`) with a generic literal fallback for an unauthored game — never another game's borrowed fiction. If the copy is generic, no new facet needed (Open Q8). --- ## 6. Fable mockup brief **Base:** extend `nv-machine-mockup.html`'s existing `?view=operations` route (currently the "SUBSYSTEM ONLINE" placeholder) so the mockup reuses the established casing / bezel / Director-column chrome and only builds the OPERATIONS boards. Fake all wiring; MetaStore/device-pref only; **zero campaign write**. (Protocol 22 — extend, don't fork. A dedicated `operations-*.html` is acceptable if cleaner, mirroring how `tempo-dial.html` / `module-bay-mockup.html` were split.) **Screens & states to render (ALL at 360 / 412 / desktop; verify `scrollWidth === innerWidth`):** 1. **OPERATIONS — healthy default:** all 6 boards; WEIGH BRIDGE + CARGO MANIFEST open, rest showing 0i `.panel-substatus` rows. The reference for the full stack. 2. **CARGO MANIFEST drawer interaction:** WEAPONS drawer open (manifest rows with USE/type/wt/val/X); a second frame with the AID drawer selected; **per-drawer count badges** visible; the **"+N more — refine search"** cap footer shown; the in-drawer search field. 3. **AMMO drawer:** the AMMO drawer selected → caliber/count/X grid + add-ammo row (proves the fold-in keeps ammo reachable). 4. **WEIGH BRIDGE states:** normal (green) / heavy ≥75% (amber) / **over-encumbered SEIZED ≥100%** (red + SHUDDER frame) — proves the gauge reacts to load. 5. **FIELD FABRICATION close-up:** recipe card with **HAVE/NEED ingredient meters** (one satisfied green, one short red), qty stepper, FABRICATE key, one craft mid-confirm; the BREAKDOWN VISE scrap sub-board. 6. **BARTER UPLINK:** amber-accented vendor header + purse gauge; buy list (afford vs dimmed-unaffordable) + sell list (vendor-can't-afford dimmed); search + cap footer. 7. **SQUAD ROSTER:** 2 companion cards with HP bars + affinity meters + `[+]`/`[-]` + ENLIST row. 8. **360 collapsed view:** every board closed to its 0i LED status row — the short-stack phone proof. 9. **Desktop:** two-up boards (`.span2`) under the glass + permanent Director column + bezel/telemetry LCD (the `nv-machine-desktop.png` framing). 10. **Optional FO3 frame** (`?view=operations` + FO3 skin): MODS drawer hidden, CURIO = BOBBLEHEADS, LINCOLN sub-panel visible, FO3 companions in ENLIST — visually proves the game-agnostic generalization before FO3 builds. 11. **(Only if Open Q3/Q6 approved as stretch):** a manifest row showing an **EQUIP** control + a loadout readout strip (Q3), and/or per-row **qty ± steppers** (Q6). **Deliverables:** the extended `nv-machine-mockup.html` (or `operations-*.html`) + PNGs at 360/412/desktop for states 1–9 + an `operations-notes.md` (like `campaign-configs-notes.md` / `operator-hero-options-notes.md`) recording the board↔real-control mapping, the id-preservation contract, tap-target/overflow verification, and build invariants. --- ## 7. The bold 1-of-1 OPERATIONS identity (why it feels like its own instrument) - **OPERATOR** = a medical/biometric telemetry rack (gauges, needles, skeletal bio-scan). **SETTINGS** = a hardware Module Bay (seated boards, hatch ceremony). **Comm-Link** = a comms oscilloscope (living waveform). **OPERATIONS** = a **quartermaster's freight console**: a **load-cell weigh bridge** you can watch strain, and a **bank of pull-drawers** you physically open to inspect cargo. It's the only screen about *mass and materiel*, and its two hero instruments (weight-as-force, cargo-as-drawers) exist nowhere else in the app. - **Shared machine language keeps it one device:** identical bezel, BUS/PN part-number series (continuing 10→15), amber-on-phosphor (amber = the remote vendor uplink), `.bay-board` frames with copper connector strips, collapsible boards, and the 0i collapsed-summary standard. It *matches* the floor; the weigh bridge + drawer bank are what push it *past* the floor. - **Recommended hero-three to build first** (mirroring OPERATOR's hero-three approach): **(1) LOAD-CELL WEIGH BRIDGE, (2) CARGO MANIFEST drawer bank, (3) FIELD FABRICATION bench with HAVE/NEED meters.** Board-frame + 0i-status shell ALL six boards in the same slice so nothing looks half-dressed; deep-dress the hero three; BARTER/SQUAD/CURIO get the board frame + lighter internal dressing, deepened in a follow-up if scope tightens. --- ## 8. Build touchpoints (for the later implementation pass — not this task) - **Protocol 22/25:** reskin only — every id/handler/state semantic preserved; render-fn template edits keep emitted ids. Owner-approved-redesign guardrails: same location/tab, no added taps (drawers = same single-select as filter; steppers are *fewer* taps than re-typing), real labels ride as sub-labels, a plain fallback preserved. - **Protocol 10/17:** render-check at 360/412/desktop, zero overflow, ≥28px/≥16px, reduced-motion-safe. - **Protocol 38:** game-agnostic (data-driven); MODS-drawer/collectibleLabel/vendors/companions gating proven; optional `identity.operations` facet. - **Protocol 20/13/15/2a:** static guards for the new drawer-bank / weigh-bridge markup contract + regression tests in **both** runners at parity; cache bump (Protocol 1) on the build commit. - **Protocol 4/UI-6:** only if a new device pref is introduced (e.g. "remember last-open drawer" — recommended as a MetaStore pref so the drawer choice survives reload per UI-6; or a qty-stepper display toggle). Weigh-bridge mirror + drawer bank need **no** new campaign state. - **New write paths (only if approved):** qty ± steppers (Q6 → `adjItemQty`) and native EQUIP (Q3 → `state.equipped` setter, closing the U10 audit gap) each need Protocol 4/13/14 treatment (state default already exists for `equipped`; the setter + regression test are the work). --- ## 9. Open questions (owner decisions) 1. **Board granularity.** Keep ~1:1 with today's 5 panels **+ the new WEIGH BRIDGE hero** (recommended — preserves per-panel persistence + scope), or consolidate (e.g. fold CURIO ARCHIVE or SQUAD elsewhere)? 2. **WEIGH BRIDGE hero (carry-weight + caps mirror).** Add it as OPERATIONS's glanceable anchor — a **read-only mirror** of `display_weight`/`caps` (source of truth stays OPERATOR, painted by the same `updateMath()` pass, zero new state)? **Recommend yes** — weight belongs where cargo lives, and it's the boldest 1-of-1 instrument. Or keep OPERATIONS weight-less (rely on OPERATOR)? 3. **Equip / unequip — the biggest scope call.** There is **no native equip control today** (`state.equipped` is AI-write-only; `#equippedDisplay` lives on OPERATOR). Options: **(A)** OPERATIONS shows a **read-only loadout mirror** strip (no regression, matches today); **(B)** OPERATIONS **adds a native EQUIP control** per manifest row (closes the U10 audit gap, "bigger ambition," but a new state-writing path needing Protocol 4/13/14). Which? 4. **Drawer-bank mobile mechanic.** Accept "one drawer open at a time + per-drawer count badge + in-drawer search" as the no-infinite-scroll solution (the filter is already single-select, so muscle memory is preserved)? Recommend yes. 5. **Manifest render cap.** Cap the open drawer at **N=50** rows with a "+N more — refine search" footer (mirrors BARTER's 40-cap) to bound the worst case? Confirm N. 6. **Per-row quantity ± steppers.** Today qty only changes via re-add(increment)/delete(remove-all)/USE/craft/trade — **no decrement-by-one**. Add `[-] qty [+]` steppers (genuine UX win + ambition, new write path) or keep current coarse behavior? Recommend as a **stretch**, safe default = current. 7. **Squad enlist select game-literal (Protocol 38).** `#newSquadName` hardcodes 8 FNV companions. Drive it from `FALLOUT_REGISTRY.companions` (exists for both games) during the dressing? **Recommend yes** (fixes an FO3 correctness bug in passing). 8. **`identity.operations` facet.** Add a per-game OPERATIONS flavor sub-block (BUS/board labels, weigh-bridge/drawer captions) now, or keep OPERATIONS copy generic (no new facet) until FO3 needs it? 9. **AMMO drawer vs separate sub-panel.** Fold AMMO RESERVES into the drawer bank as the **AMMO drawer** (cleaner, keeps ids), or keep it as a distinct nested sub-panel below the manifest? Recommend fold. 10. **CURIO ARCHIVE home.** Keep COLLECTIBLES on OPERATIONS (today) as CURIO ARCHIVE (recommended — least churn, they're materiel), or is it world-state that belongs on DATABANK? 11. **Motion budget.** Confirm drawer-slide + weigh-needle-SEAT + over-encumber SHUDDER + HAVE/NEED meter fills are wanted (all reduced-motion-safe), or keep OPERATIONS motion minimal (static gauges/instant drawers)?
STAMP · generated for RELEASE v2.8.5 commit 06e5180 (06e51801b38a) · archive input-tree hash c07fbfbdd2e1ddeb · 754 files · no wall-clock timestamp (regenerates identically when nothing changed).