Files
website/docs/superpowers/specs/2026-04-26-dedicated-drive-bays-option-b-design.md
Andrew c9e0c8826f docs(spec): drive bay Option B restructure design (deferred)
Captures the full design for splitting LFF/SFF/NVMe drive bay
groups from flat radios into Drive Selection + Drive Quantity
composite controls. Includes the SAS variants user requested,
per-drive pricing table, schema decisions (no migrations needed,
existing schema already supports multi-option groups), Pinia
store changes, new DriveBayGroupSelector component sketch, URL
param contract changes, and migration steps.

Implementation deferred to a focused next session — realistic
4-5 hour build (backend seeder + frontend component + store
rework + test rewrite). Phase A (PCIe NVMe Add-in) shipped
ahead of this in c74ca7f.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 20:04:48 -04:00

9.7 KiB
Raw Permalink Blame History

Drive Bay Configurator — Option B Restructure (deferred)

Date: 2026-04-26 Status: Approved (design only) — implementation deferred to dedicated session Owner: Andrew Repo: EZSCALE/website Related: 2026-04-26-dedicated-server-lineup-design.md (parent spec, already shipped)

Why this exists

The dedicated 14th-gen drive bay configurator (LFF / SFF / NVMe) currently uses a flat radio per chassis-bay-type group. After the recent expansion (high-cap HDDs, LFF SSDs, PCIe NVMe add-in), the LFF group alone is 35 entries on a single radio. Adding SAS variants on top would push it past 50 — UX is breaking under its own weight.

Option B splits each drive bay group into composite controls so customers pick media + capacity + quantity independently, and the frontend computes total = unit_cost × quantity. Net: ~10 drive selections + a stepper instead of 35-50 flat combo radios.

Scope (in)

  • Restructure the three existing drive bay groups (LFF, SFF, NVMe) from "one option, many flat values" to "two options per group: Drive Selection + Drive Quantity"
  • Add SAS HDD and SAS SSD variants to LFF and SFF Drive Selection lists (currently only SATA)
  • Update Pinia store to track {drive, quantity} per drive-bay group
  • New DriveBayGroupSelector component (or extend OptionGroupSelector) to render the two-option pattern
  • Update route filter to clamp Drive Quantity max to chassis bay_count
  • Update BuildSummary to render drive bay line items as N× <drive type> @ $X/drive = $Y total
  • Migrate URL params: lff_drive=, lff_qty=, sff_drive=, sff_qty=, nvme_drive=, nvme_qty=
  • Test rewrite: existing drive-bay tests need updating, add coverage for the new structure

Scope (out / deferred)

  • PCIe NVMe Add-in group keeps flat-radio pattern (combos bundle adapter+drive cost, hard to split cleanly into media+capacity+quantity). Already shipped as c74ca7f.
  • Mixed-bay heterogeneous configs (e.g., 4× HDD + 4× SSD on R540) — still ticket-only post-order. Schema doesn't natively support multi-drive-type per group.
  • Frontend animation polish on the new component
  • Per-bay-position drive picker (was descoped in original spec)

Schema decisions

No migrations needed. The existing plan_config_groups / plan_config_options / plan_config_values schema already supports multiple options per group; the seeder just wasn't using that capability for drive bays.

Each drive bay group becomes:

PlanConfigGroup::updateOrCreate(['name' => 'Dedicated 14th Gen — LFF Drive Bays'], [...]);

// Option 1: Drive Selection (radio) — values store PER-DRIVE monthly cost
$driveSelection = $this->seedRadioOption($group, 'Drive Selection', false, 1);
$this->seedValues($driveSelection, [
    ['label' => 'No drives — configure via ticket', 'value' => 'none', 'monthly' => 0, 'is_default' => true],
    ['label' => '4 TB SATA HDD',                'value' => 'sata-hdd-4tb',       'monthly' => 12.00],
    ['label' => '8 TB SATA HDD',                'value' => 'sata-hdd-8tb',       'monthly' => 20.00],
    ['label' => '12 TB Enterprise SATA HDD',    'value' => 'sata-hdd-12tb',      'monthly' => 45.00],
    ['label' => '20 TB Enterprise SATA HDD',    'value' => 'sata-hdd-20tb',      'monthly' => 55.00],
    ['label' => '24 TB Enterprise SATA HDD',    'value' => 'sata-hdd-24tb',      'monthly' => 75.00],
    ['label' => '12 TB Enterprise SAS HDD',     'value' => 'sas-hdd-12tb',       'monthly' => 50.00],  // NEW SAS
    ['label' => '16 TB Enterprise SAS HDD',     'value' => 'sas-hdd-16tb',       'monthly' => 55.00],  // NEW SAS
    ['label' => '480 GB SATA SSD (LFF carrier)','value' => 'sata-ssd-480gb-lff', 'monthly' => 10.00],
    ['label' => '1.92 TB SATA SSD (LFF carrier)','value' => 'sata-ssd-1920gb-lff', 'monthly' => 18.00],
    ['label' => '3.84 TB SATA SSD (LFF carrier)','value' => 'sata-ssd-3840gb-lff', 'monthly' => 45.00],
    ['label' => '7.68 TB SAS SSD (LFF carrier)','value' => 'sas-ssd-7680gb-lff', 'monthly' => 200.00],  // NEW SAS
    ['label' => '1.92 TB SAS SSD (LFF carrier)','value' => 'sas-ssd-1920gb-lff', 'monthly' => 40.00],  // NEW SAS
    ['label' => '3.84 TB SAS SSD (LFF carrier)','value' => 'sas-ssd-3840gb-lff', 'monthly' => 80.00],  // NEW SAS
]);

// Option 2: Drive Quantity (quantity stepper)
$driveQuantity = $this->seedQuantityOption(
    $group, 'Drive Quantity', 0, 12, 'drives', 0, 2
);
// monthly_price=0 on the option itself — actual cost computed
// in the frontend as drive_selection.monthly_price × quantity.

Note: seedQuantityOption already exists and supports min_qty / max_qty / step. Setting monthly_price=0 on it lets us reuse the helper without it interfering with our cost math.

Same pattern for SFF (Drive Selection list with SATA + SAS SSD options + the high-cap HDDs that fit, max_qty=24) and NVMe (U.2 NVMe sizes only, max_qty=24).

Per-drive pricing table (in monthly cost terms)

Drive Per-drive $/mo Notes
4 TB SATA HDD $12 Existing
8 TB SATA HDD $20 Existing
12 TB SATA HDD $45 Existing
20 TB SATA HDD $55 Existing
24 TB SATA HDD $75 Existing
12 TB SAS HDD $50 NEW — ~10% premium over SATA for 12 Gb/s + dual-port
16 TB SAS HDD $55 NEW
480 GB SATA SSD $10 Existing
1.92 TB SATA SSD $18 Existing
3.84 TB SATA SSD $45 Existing
7.68 TB SATA SSD $100 Existing — covers most "premium" cases without going SAS
1.92 TB SAS SSD $40 NEW — Nytro 3350 / 2532 class, 12 Gb/s dual-port
3.84 TB SAS SSD $80 NEW
7.68 TB SAS SSD $200 NEW
1.92 TB U.2 NVMe (SFF chassis only) $30 NEW SFF entry — fits in 2.5" SFF NVMe-capable bays where present
3.84 TB U.2 NVMe $70
7.68 TB U.2 NVMe $150

Frontend changes

Pinia store (stores/dedicatedConfigurator.ts):

// Selections shape becomes:
//   non-drive groups: { groupName: 'value-slug' }
//   drive bay groups: { groupName: { drive: 'value-slug', quantity: 0 } }

const selections = ref<Record<string, string | { drive: string; quantity: number }>>({})

function isDriveBayGroup(name: string): boolean {
  return name.endsWith('Drive Bays')
}

const driveBayCost = computed<Record<string, number>>(() => {
  const costs: Record<string, number> = {}
  for (const [groupName, sel] of Object.entries(selections.value)) {
    if (typeof sel !== 'object') continue
    const group = findGroup(groupName)
    const driveOption = group?.options.find(o => o.name === 'Drive Selection')
    const drive = driveOption?.values.find(v => v.value === sel.drive)
    if (!drive) continue
    const perDriveCost = pickCyclePrice(drive, cycle.value)
    costs[groupName] = perDriveCost * sel.quantity
  }
  return costs
})

New component Components/Marketing/Dedicated/DedicatedConfigurator/DriveBayGroupSelector.vue:

Renders both options of a drive bay group:

  • Top: radio list of Drive Selection values (uses existing OptionGroupSelector-style cards)
  • Bottom: quantity stepper (1 → max_qty), labeled Drives in chassis
  • Shows live computed cost: 2× 1.92 TB SATA SSD = +$36.00/mo

DedicatedConfigurator/index.vue switches between OptionGroupSelector (single-option groups) and DriveBayGroupSelector based on group name.

BuildSummary updates to render drive bay rows as:

LFF Drive Bays
  4× 1.92 TB SATA SSD                            +$72.00

(Currently shows the flat-combo label which works the same way visually, but the data flow changes.)

URL state contract

Drive bay groups now serialize as two params each:

Old (flat radio) New (split)
?lff=4x1920gb-ssd ?lff_drive=sata-ssd-1920gb-lff&lff_qty=4
?sff=8x3840gb-ssd ?sff_drive=sata-ssd-3840gb&sff_qty=8
?nvme=2x2tb-nvme ?nvme_drive=u2-nvme-2tb&nvme_qty=2

hydrateFromUrl reads both params per group. Old URLs with single-key drive params are silently dropped (acceptable since drive bay configs were rarely shared as URLs and the structure changed entirely).

Tests

  • Replace existing drive-bay flat-combo tests with new tests for the multi-option structure
  • Cover: drive selection persists, quantity persists, total = drive × qty, quantity max respects chassis bay_count
  • New test: SAS variants are visible only on LFF/SFF (not NVMe-native chassis)

Migration path

The seeder uses updateOrCreate keyed on [option_id, label]. Restructuring requires a one-time DB cleanup before re-seeding:

-- Before re-running ConfigOptionSeeder for the new structure
DELETE pco, pcv
FROM plan_config_options pco
LEFT JOIN plan_config_values pcv ON pcv.option_id = pco.id
JOIN plan_config_groups pcg ON pcg.id = pco.group_id
WHERE pcg.name IN (
    'Dedicated 14th Gen — LFF Drive Bays',
    'Dedicated 14th Gen — SFF Drive Bays',
    'Dedicated 14th Gen — NVMe Drive Bays'
);

Then php artisan db:seed --class=ConfigOptionSeeder recreates with new shape.

Acceptance

  • LFF / SFF / NVMe groups each have exactly 2 options (Drive Selection radio + Drive Quantity stepper)
  • LFF Drive Selection includes ≥3 SAS variants (12/16 TB SAS HDD; 1.92/3.84/7.68 TB SAS SSD)
  • SFF Drive Selection includes ≥3 SAS SSD variants
  • Per-chassis filter clamps Drive Quantity.max_qty to chassis bay_count
  • BuildSummary shows drive bay line items as N× <drive> = $Y not <combo label>
  • URL params use new _drive=&_qty= format
  • Existing 14 dedicated tests rewrite cleanly — count stays in 14-16 range
  • npm run build passes
  • Mobile (< 768 px) renders the stepper without overflow

Estimated effort

  • Backend (seeder rewrite + DB cleanup migration): ~1 hour
  • Frontend (new component + store rework): ~2 hours
  • Tests rewrite: ~1 hour
  • Verify + commit: ~30 min

Total: ~4-5 hour focused session.