Files
scrying-pool/_bmad-output/implementation-artifacts/5-2-video-widget-width-customization.md
T
uberwald 7a0d935239 Story 5.2: Video Widget Width Customization
Implements configurable video widget widths for small and large dock layouts:
- Add widgetWidthSm and widgetWidthMd world settings (defaults: 83px, 150px)
- Add width dropdown selectors in Director's Board UI
- Apply width settings to participant avatars via CSS custom properties
- Update strip width/height calculations to use custom widths
- Add localization strings for widget width settings
- Add CSS styling for widget width dropdown controls
- Fix Handlebars template syntax for select element selected values

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-05-25 12:57:24 +02:00

19 KiB

Story 5.2: Video Widget Width Customization

Status: done

Epic: 5 - Full AV Replacement

Story Key: 5-2-video-widget-width-customization

Created: 2026-05-26

Last Updated: 2026-05-26

Completed: 2026-05-26

Version: v0.1.1


Story Header

Field Value
Epic 5 - Full AV Replacement
Story ID 5.2
Story Key 5-2-video-widget-width-customization
Title Video Widget Width Customization
Status ready-for-dev
Priority Medium
Assigned Agent DEV (Mistral Vibe / Morr)
Created 2026-05-26
Last Updated 2026-05-26
Target Version v0.1.1

📋 Story Requirements

User Story

As a GM using Video View Manager, I want to select custom widths for small and large video widgets in the dock, So that I can optimize the display to match my screen space and table preferences.

Persona Alignment

  • Primary: GM (Marcus, Jake) - Needs flexibility to customize video display for different screen configurations and table setups
  • Secondary: Players - Benefit from consistent, appropriately-sized video feeds that match the GM's layout preferences

Business Value

This feature extends the Dock Layout System (Epic 5, Story 5-1) by adding granular control over video widget dimensions. It addresses user feedback requesting the ability to customize video tile sizes to better match their specific display needs and table aesthetics.

Acceptance Criteria (BDD Format)

AC-1: Widget Width Settings

Given the Video View Manager module is active When the GM opens the Director's Board Then there are dropdown selectors for small and large widget widths And the dropdowns contain the following options: 60px, 80px, 100px, 120px, 150px, 200px

AC-2: Default Width Values

Given a fresh installation of Video View Manager When no width settings have been configured Then the default width for small widgets is 80px And the default width for large widgets is 120px

AC-3: Width Setting Application

Given the GM has selected width values in the Director's Board When the GM saves the settings Then all video widgets in the dock are re-rendered with the new widths And small layout variants (vertical-sm, horizontal-sm, mosaic-sm) use the small width setting And large layout variants (vertical-md, horizontal-md, mosaic-md) use the large width setting

AC-4: Width Persistence

Given the GM has configured custom widget widths When the page is refreshed or the session is restarted Then the widget widths persist and are applied automatically And the dropdowns show the previously selected values

AC-5: Per-Layout Width Application

Given the GM has selected different widths for small and large When the dock is using a small layout variant Then all video widgets use the small width setting And when the dock is using a large layout variant Then all video widgets use the large width setting

AC-6: Real-Time Preview

Given the Director's Board is open When the GM changes a width dropdown value Then the video widgets in the dock update in real-time (within 500ms) And no page refresh is required

AC-7: Width Setting Validation

Given the width dropdown is displayed When the GM selects a width value Then the value is validated as one of the allowed options And invalid values are rejected with a warning message And the previous valid value is retained

AC-8: CSS Properly Scoped

Given custom widths are applied When the video widgets are rendered Then all width-related CSS is properly scoped under .sp-participant-item or .sp-participant-video And no unintended CSS conflicts occur with other modules

AC-9: Null Safety Throughout

Given any DOM query returns null When width application methods are called Then no TypeError is thrown And appropriate warnings are logged to console


🎯 Functional Requirements

  • FR-33: Video widget width configuration — The module provides configurable width options for small and large video tiles via world-scoped settings widgetWidthSm and widgetWidthMd
  • FR-34: Width selection dropdown in Director's Board — The Director's Board includes dropdown UI controls for selecting video widget widths for both small and large sizes

🎯 Implementation Details

Files to Modify

File Changes Status
module.js Register widgetWidthSm and widgetWidthMd world settings with onChange callbacks Pending
src/ui/gm/DirectorsBoard.js Add width dropdown selectors to settings panel, add handler for width changes Pending
src/ui/gm/ScryingPoolStrip.js Apply width settings to video elements, pass width values to template context Pending
templates/directors-board.hbs Add dropdown UI for width selection in settings panel Pending
styles/components/_roster-strip.less Use width settings for .sp-participant-item sizing Pending
lang/en.json Add localization strings for width settings Pending

Technical Implementation

Settings Registration (module.js)

// World-scoped settings for widget widths
adapter.settings.register("widgetWidthSm", {
  scope: "world",
  config: false,
  type: String,
  default: "80",
  onChange: () => roleRenderer?.rerenderStrip(),
});

adapter.settings.register("widgetWidthMd", {
  scope: "world",
  config: false,
  type: String,
  default: "120",
  onChange: () => roleRenderer?.rerenderStrip(),
});

Director's Board UI (DirectorsBoard.js)

// Add to _prepareContext()
const widgetWidthSm = this._adapter.settings?.get?.('widgetWidthSm') ?? '80';
const widgetWidthMd = this._adapter.settings?.get?.('widgetWidthMd') ?? '120';

const WIDTH_OPTIONS = [
  { value: '60', label: '60px' },
  { value: '80', label: '80px' },
  { value: '100', label: '100px' },
  { value: '120', label: '120px' },
  { value: '150', label: '150px' },
  { value: '200', label: '200px' },
];

// Add to context
return {
  ...base,
  widthOptions: WIDTH_OPTIONS,
  widgetWidthSm,
  widgetWidthMd,
};

// Add handler in _onClickButton()
case 'set-widget-width-sm': this._onSetWidgetWidth(btn.dataset.value, 'sm'); break;
case 'set-widget-width-md': this._onSetWidgetWidth(btn.dataset.value, 'md'); break;

// New method
async _onSetWidgetWidth(value, size) {
  if (!value) return;
  const settingKey = size === 'sm' ? 'widgetWidthSm' : 'widgetWidthMd';
  try {
    await this._adapter.settings?.set?.(settingKey, value);
  } catch (err) {
    console.error('[ScryingPool] Failed to set widget width:', err);
  }
  if (this.rendered) this.render({ force: true });
}

ScryingPoolStrip Context (ScryingPoolStrip.js)

// Add to _prepareContext()
const widgetWidthSm = this._adapter.settings?.get?.('widgetWidthSm') ?? '80';
const widgetWidthMd = this._adapter.settings?.get?.('widgetWidthMd') ?? '120';
const dockLayout = this.context?.dockLayout ?? 'vertical-sm';
const isLarge = dockLayout.endsWith('-md');
const effectiveWidth = isLarge ? widgetWidthMd : widgetWidthSm;

return {
  ...base,
  widgetWidth: effectiveWidth,
  isLarge,
};

Template Updates (directors-board.hbs)

{{!-- In settings panel --}}
<div class="sp-settings-group">
  <label>Video Widget Widths</label>
  
  <div class="sp-settings-row">
    <span>Small:</span>
    <select data-action="set-widget-width-sm">
      {{#each widthOptions}}
        <option value="{{this.value}}" {{../widgetWidthSm == this.value ? "selected" : ""}}>
          {{this.label}}
        </option>
      {{/each}}
    </select>
  </div>
  
  <div class="sp-settings-row">
    <span>Large:</span>
    <select data-action="set-widget-width-md">
      {{#each widthOptions}}
        <option value="{{this.value}}" {{../widgetWidthMd == this.value ? "selected" : ""}}>
          {{this.label}}
        </option>
      {{/each}}
    </select>
  </div>
</div>

CSS Updates (components/_roster-strip.less)

// Apply width to participant items
.sp-participant-item {
  width: var(--widget-width, 80px);
  min-width: var(--widget-width, 80px);
  
  // Or use inline style from template
  // width: {{widgetWidth}}px;
  // min-width: {{widgetWidth}}px;
}

// Ensure video elements fill the width
.sp-participant-video {
  width: 100%;
  height: 100%;
}

🏗️ Architecture Compliance

Design Token Usage

All width values use CSS pixel units (px) for consistency with FoundryVTT conventions No direct Foundry --color-*/--font-*/--border-* tokens used All selectors properly scoped under .sp-* prefix

Import Boundaries

No direct game.* access in core logic All Foundry API access via FoundryAdapter Settings access via adapter.settings Dependency injection maintained for all managers

Code Conventions

JSDoc on all exported symbols Private methods prefixed with _ Consistent error handling pattern (try-catch with console.error) Null-safe access patterns throughout Input validation on all user-provided values


🧪 Testing Requirements

Unit Tests

Test File: tests/unit/ui/gm/VideoWidgetWidth.test.js (new file)

Test Coverage:

  • Settings registration with proper defaults
  • Dropdown rendering with all width options
  • Width selection handler updates settings correctly
  • onChange callback triggers strip re-render
  • Small and large widths apply correctly based on layout
  • Context includes correct width value
  • Null safety for missing settings
  • Input validation rejects invalid width values

Manual Test Cases

  1. Default Widths

    • Install fresh module
    • Verify small width defaults to 80px
    • Verify large width defaults to 120px
    • Verify video widgets render at default sizes
  2. Width Selection

    • Open Director's Board
    • Navigate to settings panel
    • Verify small and large width dropdowns are present
    • Verify all width options (60, 80, 100, 120, 150, 200) are available
  3. Real-Time Preview

    • Select a different small width (e.g., 100px)
    • Verify video widgets update within 500ms
    • Verify no page refresh is required
    • Select a different large width (e.g., 150px)
    • Switch to a large layout variant
    • Verify widgets use large width
  4. Layout Switching

    • Set small width to 80px, large width to 150px
    • Switch to vertical-sm layout
    • Verify widgets use 80px width
    • Switch to vertical-md layout
    • Verify widgets use 150px width
    • Switch back to vertical-sm
    • Verify widgets return to 80px width
  5. Persistence

    • Configure custom widths
    • Refresh the page
    • Verify widths persist
    • Restart FoundryVTT
    • Verify widths persist
  6. Input Validation

    • Attempt to set invalid width value via console
    • Verify value is rejected
    • Verify previous valid value is retained
    • Verify warning is logged to console

📚 Developer Context Section

What the Developer MUST Know

1. Settings Pattern

Critical: This story uses the established world-scoped setting pattern from previous stories.

Pattern:

adapter.settings.register("settingName", {
  scope: "world",
  config: false,
  type: String,
  default: "defaultValue",
  onChange: () => callback(),
});

Do NOT:

  • Use client-scoped settings (width should be consistent for all users)
  • Forget the onChange callback (strips won't update automatically)
  • Use numeric type (stored as string to maintain precision)

DO:

  • Always provide sensible defaults
  • Always include onChange callback to trigger UI updates
  • Use string type for pixel values to avoid type issues

2. Dock Layout Integration

This feature extends the existing Dock Layout System (FR-29-30), not replaces it.

Integration Points:

  • Width settings are independent of layout direction (vertical/horizontal/mosaic)
  • Width settings depend on size variant (sm/md)
  • Layout selection (FR-29) and width selection (FR-33) work together

Flow:

  1. GM selects layout direction + size (e.g., vertical-md)
  2. System determines if layout is small or large variant
  3. System applies corresponding width setting (widgetWidthSm or widgetWidthMd)
  4. Strip re-renders with new layout AND width

3. Real-Time Update Pattern

Critical: Width changes must update video widgets in real-time without page refresh.

Pattern:

onChange: () => roleRenderer?.rerenderStrip()

Why:

  • Provides immediate visual feedback
  • Matches existing pattern from Dock Layout System (FR-29)
  • Expected by users from modern web applications

DO:

  • Use onChange callback on settings registration
  • Call rerenderStrip() when width changes
  • Ensure rerender includes updated width in context

4. Dropdown UI Pattern

Critical: Follow existing Director's Board UI patterns for consistency.

Pattern:

  • Use <select> element with data-action attribute
  • Include all options with value and label
  • Show current value as selected
  • Handle change in _onClickButton or similar method

Reference: See existing dock layout selector in DirectorsBoard.js (FR-29 implementation)

5. CSS Scoping Pattern

Critical: All CSS must be properly scoped to avoid conflicts.

Pattern:

.sp-participant-item {
  // Scoped styles here
}

Why:

  • Prevents conflicts with other modules
  • Follows FoundryVTT best practices
  • Maintains modular CSS architecture

DO:

  • Use .sp-* prefix for all custom classes
  • Avoid global selectors
  • Use inline styles from template if dynamic values needed

Story Dependency Type Reason
1-3-data-layer Required FoundryAdapter infrastructure for settings
1-5-gm-control-ui Required ScryingPoolStrip base component
2-2-directors-board Required Director's Board UI infrastructure
4-7-dock-layout Required Dock Layout System foundation
5-1-full-av-replacement Required Full AV Replacement with video elements

📊 Success Criteria

Criterion Target Measurement
Code Quality 0 lint errors ESLint run
Test Coverage All major paths tested Manual + unit tests
User Experience Real-time updates within 500ms Manual testing
Settings Validation No invalid values accepted Error handling tests
CSS Conflicts Zero conflicts with other modules Integration testing
Persistence 100% retention across sessions Manual testing

🎯 Previous Story Intelligence

Patterns Established in Story 5-1 (Full AV Replacement)

  • World-scoped settings with onChange callbacks
  • Director's Board UI integration
  • Strip re-rendering pattern
  • Video element styling with CSS
  • Null safety throughout

Lessons Applied to This Story

  1. Settings Pattern: Reusing world-scoped setting registration from Story 5-1
  2. UI Pattern: Following Director's Board UI conventions from Story 5-1
  3. Re-render Pattern: Using onChange callback to trigger strip updates
  4. CSS Pattern: Properly scoped CSS for video elements
  5. Validation Pattern: Input validation for all user-provided values

🚀 Git Intelligence

Related Commits:

Files Modified in Similar Stories:

  • module.js - Settings registration
  • src/ui/gm/DirectorsBoard.js - UI controls
  • src/ui/gm/ScryingPoolStrip.js - Context and rendering
  • templates/directors-board.hbs - Template updates
  • styles/components/_roster-strip.less - CSS styling
  • lang/en.json - Localization

📖 Technical Information

FoundryVTT Settings API

Methods Used:

game.settings.register(module, key, config)
game.settings.get(module, key)
game.settings.set(module, key, value)

Config Options:

  • scope: "world" (GM-controlled) or "client" (user-specific)
  • config: true (show in settings UI) or false (hidden, programmatic only)
  • type: String, Number, Boolean, Object
  • default: Default value
  • onChange: Callback function when value changes

CSS Styling Approach

Recommended: Use template variables for dynamic widths

<div class="sp-participant-item" style="width: {{widgetWidth}}px; min-width: {{widgetWidth}}px;">

Alternative: Use CSS custom properties

// In _prepareContext
document.documentElement.style.setProperty('--widget-width', `${width}px`);

// In CSS
.sp-participant-item {
  width: var(--widget-width, 80px);
}

Action Items & Next Steps

Implementation Tasks

  • Register widgetWidthSm and widgetWidthMd settings in module.js
  • Add dropdown UI to Director's Board template
  • Add width dropdown handlers in DirectorsBoard.js
  • Pass width values to ScryingPoolStrip context
  • Apply width values to video elements in template
  • Update CSS to respect width settings
  • Add localization strings for width settings
  • Add input validation for width values
  • Create unit tests for width functionality
  • Manual testing of all acceptance criteria

Cross-Story Coordination

  • Verify integration with Dock Layout System (Story 5-1)
  • Verify no conflicts with existing settings
  • Verify proper scoping of CSS changes
  • Update Director's Board documentation

Release Preparation

  • Update module.json version to 0.1.1
  • Add feature to release notes
  • Update user documentation
  • Test with various FoundryVTT module combinations

🎉 Story Completion Status

Task Status Notes
Story requirements extracted From PRD FR-33, FR-34
Epic context loaded Epic 5: Full AV Replacement
Architecture analysis Follows existing patterns
Previous story intelligence Patterns from Stories 1-3, 2-2, 5-1
Git intelligence Similar patterns from Story 5-1
Technical research FoundryVTT settings API validated
Story file created Complete documentation
Sprint status synced To be updated

Status: ready-for-dev
Completion Note: Ultimate context engine analysis completed - comprehensive developer guide created


📝 Project Context Reference

  • Project: video-view-manager (scrying-pool)
  • Epic: 5 - Full AV Replacement
  • Story: 5.2 - Video Widget Width Customization
  • Version: v0.1.1
  • FoundryVTT Compatibility: v14+
  • User: Morr
  • PRD Location: _bmad-output/planning-artifacts/prds/prd-video-view-manager-2026-05-19/prd.md
  • Architecture: _bmad-output/planning-artifacts/architecture.md
  • UX Design: _bmad-output/planning-artifacts/ux-design-specification.md
  • Implementation Artifacts: _bmad-output/implementation-artifacts/
  • Tests: tests/unit/

  • PRD: §4.7.5 Video Widget Width Customization
  • Functional Requirements: FR-33, FR-34
  • Decision: D-22 in .decision-log.md
  • Open Question: OQ-8 in PRD
  • Assumption: §9 Assumptions Index

Story 5.2 is ready for development. All context, requirements, and implementation guidance provided.