Skip to content

plan

Implementation Plan: Funding Stream Balance in Package Sidebar

Feature Code: FRB (Funding Remaining Balance) Epic: TP-2501 (Budget Reloaded) Spec: spec.md Created: 2025-12-09 Status: Draft


Technical Context

Technology Stack

  • Backend: Laravel 12 (PHP 8.3.28)
  • Frontend: Vue 3 + Inertia.js v2, TypeScript
  • Styling: Tailwind CSS v3
  • Package Manager: npm, composer
  • Testing: Pest v3 (feature & unit tests)
  • Database: MySQL (read-only queries for funding data)

Critical Dependencies

  1. FundingStreamData - Existing DTO with availableAmount, externalId, colour properties
  2. FundingStreamBadge.vue - Existing Vue component for badge rendering
  3. PackageViewData - Distributed to all package tabs via Inertia
  4. BudgetPlanController - Quarter determination logic (existing)
  5. Quarter model - Quarter::getCurrent() static method
  6. BudgetPlan model - getSelectableQuarters() method
  7. Package model - Relationship to BudgetPlan
  8. PackageSidebar.vue - Target component for integration

Constraints

  • Performance: No new database queries; data pre-fetched with package view data
  • Latency: Must load within standard package view latency (<500ms)
  • Accessibility: Badge descriptions required; colors not sole identifier
  • Compatibility: Must work on all package view tabs (11 tabs total)
  • Styling: Match existing funding UI patterns; support dark mode if sidebar has it
  • Data Accuracy: Funding amounts must match Service Plan view exactly

Known Limitations

  • Quarter determination reuses existing Service Plan logic (no new quarter calculations needed)
  • No real-time updates; relies on Inertia re-renders during tab navigation
  • Empty state requires explicit styling (not just hidden)

Design Decisions

1. Data Architecture

Decision: Extend PackageViewData to include funding summary, calculated once per page load.

Rationale:

  • Avoids N+1 queries; data already loaded for Service Plan
  • Reuses existing quarter determination logic from BudgetPlanController
  • Single source of truth: funding data matches Service Plan view
  • No new database access patterns needed

Implementation:

  • Add fundingSummary property to PackageViewData DTO
  • Populate in PackageService::getTabData() method
  • Include full FundingStreamData collection for current quarter

2. Quarter Determination Strategy

Decision: Mirror Service Plan’s quarter selection logic exactly.

Rationale:

  • Coordinator sees consistent funding across all tabs
  • No duplicate logic; reuse existing quarter determination
  • Handles edge cases (between quarters, future quarters, etc.)
  • Automatically respects ?quarter=X URL parameter

Implementation Flow:

1. Check URL parameter (?quarter=X)
2. If not set, use Quarter::getCurrent()
3. If current not selectable, use package commencement quarter
4. Fallback to budget plan commencement quarter

Key Integration Points:

  • Extract quarter selection from BudgetPlanController::show() into reusable service
  • Or: Call same logic from both BudgetPlanController and PackageService

3. Component Architecture

Decision: Create new lightweight FundingStreamBalanceSidebar.vue component that wraps existing FundingStreamBadge.vue.

Rationale:

  • Keeps sidebar logic modular and testable
  • Reuses battle-tested badge component
  • Easier to maintain and extend
  • Separates presentation from data logic

File Structure:

resources/js/Components/Package/Sidebar/
├── FundingStreamBalanceSidebar.vue (NEW - wrapper component)
├── PackageSidebar.vue (EXISTING - parent component)
└── ...

4. Empty State Handling

Decision: Display section with “No Funding Streams” message instead of hiding.

Rationale:

  • Users understand feature exists but no budget plan yet
  • Consistent sidebar layout across packages
  • Clear signal action needed (create budget)
  • Professional, non-error presentation

Styling Approach:

  • Centered text with subtle color (muted gray)
  • Same section container as funding display
  • Uses existing empty state patterns from app

5. Badge Layout Strategy

Decision: Two-column horizontal grid layout with responsive wrapping.

Rationale:

  • Matches design version B mockup exactly
  • Compact sidebar footprint (2 badges per row)
  • Responsive: wraps to 1 column on mobile
  • Scales gracefully (no limit on stream count)
  • Clean visual hierarchy

Tailwind Implementation:

  • Section title: “This Quarter’s Funding”
  • Grid container: grid grid-cols-2 gap-3 (2 columns, responsive gap)
  • Mobile breakpoint: md:grid-cols-2 grid-cols-1 (1 column below medium breakpoint)
  • Each badge: [Code] $[Amount] format only
  • Tooltip on badge: Full stream name + quarter range

6. Tooltip Content Strategy

Decision: Hide detailed information in tooltips; badge shows code + amount only.

Rationale:

  • Clean, minimal sidebar display
  • Full context available without clutter
  • Consistent with design mockup (code + amount visible, rest in tooltip)
  • Improves sidebar readability

Tooltip Information:

  • Full funding stream name (e.g., “Home support ongoing”)
  • Quarter display (e.g., “Q3 2025: July - September 2025”)
  • Additional context as needed

Data Model

Funding Summary DTO

New File: domain/Budget/Data/PackageFundingSummaryData.php

class PackageFundingSummaryData extends Data
{
public function __construct(
public ?string $currentQuarterDisplay, // "Q3 2025" for tooltip
public Collection $fundingStreams, // Collection of FundingStreamData
) {}
}

Where It Lives:

  • In PackageViewData, add property: public ?PackageFundingSummaryData $fundingSummary
  • Populated during PackageService::getTabData() call

Modified DTOs

PackageViewData (domain/Package/Data/PackageViewData.php):

  • Add property: public ?PackageFundingSummaryData $fundingSummary = null
  • Populated by new action: GetPackageFundingSummary

No Changes Needed:

  • FundingStreamData - Already has all required fields
  • BudgetPlan model - Already has quarter relationships
  • Quarter model - Already has getCurrent() method

API Contracts

No New Endpoints Required

Rationale: Funding data packaged with existing PackageViewData response.

Updated Response Structure

Route: All package view routes (e.g., GET /packages/{package})

Response Change:

{
"package": { ... },
"viewLayoutData": {
"cash_balance": "...",
"funding_summary": {
"currentQuarterDisplay": "Q3 2025",
"fundingStreams": [
{
"externalId": "ON",
"name": "Office of the National Coordinator",
"colour": "#1f2937",
"availableAmount": 15302.45,
"totalFunded": 100000.00,
"spent": 84697.55,
...
},
{ "externalId": "CM", "name": "Care Management", ... }
]
}
},
...
}

Backward Compatibility

  • fundingSummary optional (can be null)
  • All existing response fields unchanged
  • Safe to add without breaking clients

UI Components

Component Hierarchy

PackageLayout (parent)
└── PackageSidebar
├── RecipientInformation (EXISTING)
├── FundingStreamBalanceSidebar (NEW)
│ ├── Section Title
│ ├── Empty State (conditional) OR
│ └── FundingStreamBadges (collection)
│ └── FundingStreamBadge (EXISTING - reused)
│ ├── Badge Icon/Color
│ ├── Stream Code (ON, CM)
│ └── Amount ($15,302.45)
└── ActionButtons (EXISTING)

New Component: FundingStreamBalanceSidebar.vue

Location: resources/js/Components/Package/Sidebar/FundingStreamBalanceSidebar.vue

Props:

interface Props {
fundingStreams?: FundingStreamData[]
quarterDisplay?: string // e.g., "Q3 2025" for tooltip context
}

Layout Structure:

  • Section title: “This Quarter’s Funding”
  • If no funding streams: “No Funding Streams” empty state (centered)
  • If funding streams: 2-column grid layout with badges

Grid Layout:

<div class="grid grid-cols-2 gap-3 md:grid-cols-2 md:gap-4">
<!-- Badges render here, 2 per row -->
</div>

Features:

  • Badge format: [StreamCode] $[Amount] only
  • Tooltip on each badge showing:
    • Full funding stream name
    • Quarter range (e.g., “Q3 2025: July - September 2025”)
    • Any additional context
  • Accessible descriptions for screen readers
  • Dark mode support (inherit from sidebar)
  • Responsive: Wraps to 1 column on mobile, 2 columns on desktop

Integration with PackageSidebar.vue

File: resources/js/Components/Package/Sidebar/PackageSidebar.vue

Changes:

  1. Import new component: import FundingStreamBalanceSidebar from './FundingStreamBalanceSidebar.vue'
  2. Add to template after RecipientInformation section:
<FundingStreamBalanceSidebar
:funding-streams="viewLayoutData.funding_summary?.fundingStreams"
:quarter-display="viewLayoutData.funding_summary?.currentQuarterDisplay"
/>
  1. Conditional render: Only if viewLayoutData.funding_summary exists

Implementation Phases

Phase 0: Research & Preparation

Duration: 2-3 hours

  • Review existing FundingStreamData structure and usage
  • Examine BudgetPlanController quarter selection logic in detail
  • Check PackageService::getTabData() flow and extension points
  • Review FundingStreamBadge.vue component API and styling
  • Determine optimal place to extract quarter logic (new service or inline)
  • Verify no side effects from adding fundingSummary to PackageViewData

Deliverables:

  • Design decision memo on quarter logic extraction
  • DTO design finalized
  • Component API specification

Phase 1: Backend Foundation

Duration: 4-6 hours

Tasks:

  1. Create PackageFundingSummaryData DTO

    • File: domain/Budget/Data/PackageFundingSummaryData.php
    • Properties: currentQuarterDisplay, fundingStreams (Collection)
    • Validation: Ensure fundingStreams is non-empty or null
  2. Create GetPackageFundingSummary Action

    • File: domain/Budget/Actions/GetPackageFundingSummary.php
    • Input: Package model
    • Logic:
      • Get latest BudgetPlan for package
      • If no plan, return null
      • Determine current quarter using Service Plan logic
      • Extract FundingStreamData for that quarter
      • Format quarter display string (e.g., “Q3 2025”)
    • Output: ?PackageFundingSummaryData
  3. Extract/Refactor Quarter Selection Logic (if needed)

    • Option A: Create reusable GetCurrentQuarterForBudgetPlan service
    • Option B: Add method to BudgetPlan model
    • Option C: Duplicate logic (acceptable if isolated)
    • Selected: Option A (reusable service)
  4. Update PackageViewData DTO

    • File: domain/Package/Data/PackageViewData.php
    • Add property: public ?PackageFundingSummaryData $fundingSummary = null;
  5. Modify PackageService::getTabData()

    • File: domain/Package/Services/PackageService.php
    • Call GetPackageFundingSummary::run($package)
    • Assign result to packageViewData->fundingSummary
    • Handle null gracefully

Testing:

  • Unit test: GetPackageFundingSummary with various package states
  • Feature test: Package view returns correct funding summary
  • Edge cases: No budget plan, single stream, multiple streams, zero balances

Phase 2: Frontend Components

Duration: 3-4 hours

Tasks:

  1. Create FundingStreamBalanceSidebar.vue

    • File: resources/js/Components/Package/Sidebar/FundingStreamBalanceSidebar.vue
    • Props: fundingStreams, quarterDisplay
    • Template:
      • Section heading: “Remaining balance by funding stream”
      • Conditional render:
        • If no streams: Empty state “No Funding Streams”
        • If streams: Loop and render FundingStreamBadge for each
      • Styling: Tailwind flex/gap, dark mode support
    • Features:
      • Tooltip with full stream name on hover
      • Badge descriptions for accessibility
      • Responsive: Stack all sizes, no breaks
  2. Integrate into PackageSidebar.vue

    • File: resources/js/Components/Package/Sidebar/PackageSidebar.vue
    • Import new component
    • Add to template with conditional render
    • Position: Below RecipientInformation section
    • Props: Map from viewLayoutData.funding_summary
  3. Styling & Theming

    • Use existing FundingStreamBadge colors
    • Apply Tailwind gap utilities (gap-3 or gap-4)
    • Empty state: Center text, muted color (#6B7280 or similar)
    • Support dark mode (check if sidebar has dark mode class)

Testing:

  • Component tests: Props, conditional rendering, empty states
  • Snapshot tests: Verify badge layout
  • Visual tests: Dark mode, multiple streams, long names
  • Accessibility tests: Screen reader descriptions, color contrast

Phase 3: Integration & Refinement

Duration: 3-4 hours

Tasks:

  1. End-to-End Testing

    • Navigate all package tabs, verify funding visible everywhere
    • Modify budget in Service Plan, navigate back, verify update
    • Test with zero balances, negative balances
    • Test with long funding stream names (tooltip)
    • Test on packages without budget plans
  2. Performance Testing

    • Measure page load time (sidebar should not add latency)
    • Verify no N+1 queries
    • Profile funding data retrieval
  3. Code Formatting & Standards

    • Run vendor/bin/pint --dirty for PHP
    • Verify Prettier formatting for Vue
    • Check ESLint rules
    • Ensure no unused imports
  4. Documentation

    • Add inline comments to GetPackageFundingSummary action
    • Document DTO structure
    • Update any relevant README or docs

Testing:

  • Full test suite: php artisan test
  • Manual QA across all package types
  • Browser testing: Chrome, Safari, Firefox
  • Mobile testing: Responsive sidebar layout

Phase 4: Deployment Preparation

Duration: 1-2 hours

Tasks:

  1. Final Review

    • Code review: Check for edge cases, error handling
    • Spec compliance: Verify all requirements met
    • Performance: Confirm no regressions
  2. Create Changeset Summary

    • List all files modified
    • Document breaking changes (none expected)
    • Note any configuration changes needed (none)
  3. Prepare Migration Plan (if any DB changes needed)

    • Confirm: No migrations needed for this feature
    • Data already exists in existing schema

Risk Assessment

RiskLikelihoodImpactMitigation
Quarter logic mismatchMediumHighExtract shared quarter service; mirror BudgetPlanController exactly; test all quarter edge cases
Stale data on concurrent tabsLowMediumDocument limitation; users expect re-render on tab change; can add focus event listener if needed
Performance regressionLowMediumPre-fetch all data; no new queries; load test with 1000+ packages
Funding data calculation errorLowHighInherit existing FundingStreamData logic (proven); extensive test coverage; compare with Service Plan view
Missing null safetyLowMediumExplicit null checks in template; fundingSummary optional in DTO; null coalescing in Vue
Accessibility issuesLowMediumTooltip text for screen readers; color + text in badge; test with accessible name checker
UI layout breaksLowMediumTest on small screens; Tailwind flex utilities scale well; responsive badge sizing
Empty state appearanceVery LowLowSimple centered text; test styling matches other empty states in app

Mitigation Strategy:

  • Comprehensive test suite with edge case coverage
  • Code review focusing on data accuracy
  • Performance profiling before merge
  • Manual QA on all 11 package tabs

Testing Strategy

Unit Tests

File: tests/Unit/Budget/GetPackageFundingSummaryTest.php

Test Cases:

  • ✅ Package with no budget plan returns null
  • ✅ Package with budget plan returns PackageFundingSummaryData
  • ✅ Funding streams collection non-empty when plan exists
  • ✅ Quarter display formatted correctly
  • ✅ Single funding stream renders correctly
  • ✅ Multiple funding streams all included
  • ✅ Zero balance streams included
  • ✅ Negative balance streams included

Feature Tests

File: tests/Feature/Package/PackageFundingSidebarTest.php

Test Cases:

  • ✅ Overview tab shows funding summary
  • ✅ Care Circle tab shows funding summary
  • ✅ All 11 tabs show funding summary
  • ✅ Funding matches Service Plan view exactly
  • ✅ Empty state shows “No Funding Streams”
  • ✅ Funding updates after budget modification
  • ✅ Quarter parameter respected (?quarter=X)
  • ✅ Access control: Users can view package funding if they can view package

Component Tests (Vue)

File: tests/Unit/Components/FundingStreamBalanceSidebarTest.ts (Vitest or similar)

Test Cases:

  • ✅ Component renders when fundingStreams provided
  • ✅ Empty state renders when no fundingStreams
  • ✅ Badge rendered for each stream
  • ✅ Tooltip contains full stream name
  • ✅ Amount formatted correctly with currency
  • ✅ Accessible descriptions present
  • ✅ Dark mode class applied
  • ✅ Responsive layout works on mobile

Manual QA Checklist

  • Funding visible on Overview tab
  • Funding visible on all 11 package tabs
  • Empty state shows for package without budget
  • Modify budget in Service Plan, check sidebar updates
  • Test with 1-8+ funding streams
  • Test long funding stream names (tooltip)
  • Test zero and negative balances
  • Test on mobile (responsive layout)
  • Test in dark mode
  • Test with screen reader (NVDA/JAWS on Windows, VoiceOver on Mac)
  • Test currency formatting edge cases

Implementation Files & Changes

New Files

FileTypeLinesPurpose
domain/Budget/Data/PackageFundingSummaryData.phpPHP DTO~20Data structure for funding summary
domain/Budget/Actions/GetPackageFundingSummary.phpPHP Action~50Business logic to fetch funding
resources/js/Components/Package/Sidebar/FundingStreamBalanceSidebar.vueVue~100Funding display component
tests/Unit/Budget/GetPackageFundingSummaryTest.phpPest Test~80Unit tests
tests/Feature/Package/PackageFundingSidebarTest.phpPest Test~120Feature tests

Modified Files

FileChangesImpact
domain/Package/Data/PackageViewData.phpAdd fundingSummary propertyOptional; backward compatible
domain/Package/Services/PackageService.phpCall GetPackageFundingSummary in getTabData()Low impact; data already available
resources/js/Components/Package/Sidebar/PackageSidebar.vueImport and render FundingStreamBalanceSidebarLow impact; conditional render

Total Code Impact

  • New Code: ~350 lines (PHP + Vue)
  • Modified Code: ~20 lines (imports + one method call)
  • Test Code: ~200 lines
  • No Breaking Changes

Dependencies & Prerequisites

Code Dependencies

  • FundingStreamData - Already exists
  • FundingStreamBadge.vue - Already exists
  • BudgetPlan model with quarter methods - Already exists
  • Quarter::getCurrent() - Already exists
  • PackageSidebar.vue - Already exists
  • ✅ Inertia.js v2 - Already in use

External Dependencies

  • None new required
  • Tailwind CSS v3 (already used)
  • Vue 3 (already used)

Knowledge Requirements

  • Understanding of Laravel DTOs (Data Transfer Objects)
  • Understanding of Action pattern (already used in codebase)
  • Vue 3 template syntax
  • Tailwind CSS utilities

Constitution Alignment

Status: ✅ Aligned with project principles

PrincipleStatusNotes
Follow Laravel conventionsUses Models, Services, Actions, DTOs per codebase patterns
Reuse existing componentsExtends PackageSidebar, reuses FundingStreamBadge, follows existing patterns
No new dependenciesUses only existing packages (Laravel, Vue, Tailwind)
Comprehensive testingUnit, feature, and component test coverage planned
Performance consciousNo new queries; pre-fetched data; leverages Inertia caching
Accessible UXTooltips, badge descriptions, color + text, dark mode support
Code qualityFollows PSR-12, uses type hints, consistent naming

Deployment Strategy

Pre-Deployment

  1. Run full test suite: php artisan test
  2. Code review: Check data accuracy, edge cases
  3. Performance test: Measure page load times
  4. Browser testing: Chrome, Safari, Firefox

Deployment Steps

  1. Merge to main/dev branch
  2. Run migrations (none needed)
  3. Clear cache: php artisan cache:clear
  4. Deploy to staging
  5. Smoke test: Verify funding appears on all package tabs
  6. Deploy to production
  7. Monitor: Check for errors, performance issues

Rollback Plan

  • Simple revert: Feature is additive (new component in sidebar)
  • No data changes: Can safely rollback without data loss
  • Time to rollback: <5 minutes (git revert)

Next Steps

  1. Code Review of Plan

    • User approval of architecture and phasing
    • Confirm technology choices and data model design
  2. Generate Task List

    • Run /speckit.tasks to break plan into actionable tasks
    • Estimate story points per task
    • Prioritize by dependency order
  3. Design Mockups (if not already done)

    • Run /trilogy.mockup to visualize component layout
    • Confirm empty state styling
    • Verify responsive design on mobile
  4. Begin Implementation

    • Start with Phase 0 (research)
    • Then Phase 1 (backend) in parallel with Phase 2 (frontend)
    • Phase 3 (integration) after components ready
    • Phase 4 (deployment) after full testing
  5. Sync to Jira (if needed)

    • Run /trilogy.jira-sync to link tasks to epic TP-2501
    • Assign to team members
    • Track progress

Success Criteria

Implementation Success when:

  1. All unit and feature tests pass
  2. Funding visible on all 11 package tabs
  3. Sidebar data matches Service Plan view exactly
  4. No performance regression (page load time unchanged)
  5. Empty state renders beautifully for packages without budgets
  6. Tooltips show full funding stream names
  7. Responsive on mobile and desktop
  8. Accessible to screen readers
  9. Dark mode fully supported
  10. Code formatted and linted
  11. Full test coverage (>80% of new code)
  12. Zero breaking changes to existing APIs

References

  • Specification: spec.md
  • Quality Checklist: checklists/spec-quality.md
  • Existing Components:
    • FundingStreamBadge.vue: resources/js/Components/ServicePlan/Funding/FundingStreamBadge.vue
    • PackageSidebar.vue: resources/js/Components/Package/Sidebar/PackageSidebar.vue
    • BudgetPlanController: domain/Budget/Http/Controllers/BudgetPlanController.php
    • Quarter model: app/Models/Quarter.php