Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,133 @@ If you find yourself writing code that:

---

## 🚀 CRITICAL: LAZY LOADING POLICY 🚀

**MANDATORY FOR ALL COPILOT AGENTS**: Lazy loading is the **HIGHLY PREFERRED** method for loading libraries, services, components, and modules in SGeX Workbench. This is critical for performance, user experience, and preventing module initialization order issues.

### Why Lazy Loading is Critical

1. **Performance**: Reduces initial bundle size and improves time-to-interactive
2. **User Experience**: Faster page loads, especially on slow connections
3. **Module Initialization**: Prevents circular dependencies and initialization order issues
4. **Bandwidth**: Minimizes data transfer for features users may not use
5. **Scalability**: Allows the application to grow without impacting initial load time

### Lazy Loading Requirements

**YOU MUST** use lazy loading for:
- ✅ **React Components**: Use `React.lazy()` for all route-level and feature components
- ✅ **Services**: Use dynamic `import()` for heavy services or those with complex dependency chains
- ✅ **Libraries**: Defer loading of large libraries (BPMN.js, DMN.js, etc.) until needed
- ✅ **Validation Rules**: Register validation rules on-demand, not at module initialization
- ✅ **Heavy Dependencies**: Load XML/JSON parsers, formatters, etc. only when used

**YOU MUST NOT**:
- ❌ Import services at module top-level if they trigger complex initialization chains
- ❌ Load large libraries eagerly when they're only used in specific features
- ❌ Initialize singletons at module load time if they have dependencies
- ❌ Import heavy components without React.lazy() or Suspense

### Lazy Loading Patterns

#### 1. React Components (Preferred)
```javascript
// ✅ CORRECT - Lazy load with React.lazy()
const ValidationReport = React.lazy(() => import('./validation/ValidationReport'));
const BPMNEditor = React.lazy(() => import('./BPMNEditor'));

// Use with Suspense
<Suspense fallback={<div>Loading...</div>}>
<ValidationReport />
</Suspense>

// ❌ WRONG - Eager import
import { ValidationReport } from './validation/ValidationReport';
```

#### 2. Services and Libraries (Preferred)
```javascript
// ✅ CORRECT - Dynamic import with memoization
let servicePromise = null;
async function getValidationService() {
if (!servicePromise) {
servicePromise = import('../../services/validation').then(module => module.validationService);
}
return servicePromise;
}

// Use in functions/hooks
const service = await getValidationService();

// ❌ WRONG - Top-level import
import { validationService } from '../../services/validation';
```

#### 3. Heavy Libraries (Preferred)
```javascript
// ✅ CORRECT - Load BPMN.js only when needed
async function loadBPMNModeler() {
const BpmnJS = await import('bpmn-js/lib/Modeler');
return new BpmnJS.default({ container: '#canvas' });
}

// ❌ WRONG - Load at module init
import BpmnModeler from 'bpmn-js/lib/Modeler';
```

#### 4. Validation Rules (Preferred)
```javascript
// ✅ CORRECT - Register rules on first validation
let rulesRegistered = false;
export async function ensureRulesRegistered() {
if (rulesRegistered) return;
const { registerAllRules } = await import('./rules');
registerAllRules(registry);
rulesRegistered = true;
}

// ❌ WRONG - Register at module init
import { registerAllRules } from './rules';
registerAllRules(registry); // Runs immediately
```

### Exception Cases

Lazy loading may be skipped ONLY when:
1. **Critical path**: Component is needed for initial render (e.g., App.js, Router)
2. **Tiny modules**: Module is < 1KB and has no dependencies
3. **Already bundled**: Module is guaranteed to be in the same webpack chunk
4. **Explicit approval**: Code owner/maintainer explicitly approves eager loading

**Document exceptions** with clear comments explaining why lazy loading is skipped.

### Testing Lazy Loading

When implementing lazy loading:
1. **Build and inspect**: Check webpack bundle analysis for separate chunks
2. **Network tab**: Verify chunks load on-demand in browser DevTools
3. **Performance**: Measure time-to-interactive before and after
4. **Error handling**: Ensure proper error boundaries for lazy-loaded components

### Common Pitfalls to Avoid

1. **Circular dependencies**: Lazy loading often reveals circular deps - fix the architecture
2. **Forgotten Suspense**: React.lazy() requires Suspense wrapper
3. **Promise caching**: Cache dynamic import promises to avoid loading twice
4. **Module initialization side effects**: Services with side effects at module level will still run - refactor to lazy init

### Code Review Checklist

Before submitting code, verify:
- [ ] All route-level components use React.lazy()
- [ ] Heavy services use dynamic import with memoization
- [ ] No services imported at top-level if they have complex initialization
- [ ] Suspense boundaries in place for lazy components
- [ ] Build output shows separate chunks for lazy-loaded modules
- [ ] No module initialization order issues in console

---

Following these guidelines will help us code together better:
* If a branch is mentioned in a request, issue or bug report, then please update the context to refer to the branch mentioned. If no branch is mentioned, then assume it is main.
* If there is no issue mentioned in a prompt or already in context, then propose to create an issue with an appropriate summary and title.
Expand Down
78 changes: 78 additions & 0 deletions src/components/DAKValidationSection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* DAK Validation Section Component
*
* Lazy-loaded component that encapsulates all validation functionality.
* This prevents the validation services from being loaded until this component is rendered.
*/

import React, { useState } from 'react';
import { useValidation } from './validation/useValidation';
import { ValidationButton } from './validation/ValidationButton';
import { ValidationReport } from './validation/ValidationReport';
import { ValidationSummary } from './validation/ValidationSummary';

const DAKValidationSection = ({ owner, repo, branch }) => {
const [showValidationModal, setShowValidationModal] = useState(false);
const [validationComponent, setValidationComponent] = useState('all');

// Validation hook - only loaded when this component renders
const { report, loading: validating, validate } = useValidation({
owner,
repo,
branch
});

return (
<div className="dak-validation-section">
<div className="section-header">
<h3 className="section-title">DAK Validation</h3>
<p className="section-description">
Validate DAK artifacts against WHO SMART Guidelines standards. Check BPMN processes,
DMN decision tables, FSH profiles, and DAK-level compliance.
</p>
</div>

<div className="validation-controls">
<div className="component-filter">
<label htmlFor="validation-component-filter">Validate Component:</label>
<select
id="validation-component-filter"
value={validationComponent}
onChange={(e) => setValidationComponent(e.target.value)}
className="component-select"
>
<option value="all">All Components</option>
<option value="business-processes">Business Processes (BPMN)</option>
<option value="decision-logic">Decision Logic (DMN)</option>
<option value="fhir-profiles">FHIR Profiles (FSH)</option>
<option value="dak-config">DAK Configuration</option>
</select>
</div>

<ValidationButton
onClick={() => validate({ component: validationComponent === 'all' ? undefined : validationComponent })}
loading={validating}
status={report ? (report.isValid ? 'success' : (report.summary.errorCount > 0 ? 'error' : 'warning')) : undefined}
label={validating ? 'Validating...' : 'Run Validation'}
/>
</div>

{report && (
<div className="validation-results">
<ValidationSummary
report={report}
onClick={() => setShowValidationModal(true)}
/>
</div>
)}

<ValidationReport
report={report}
isOpen={showValidationModal}
onClose={() => setShowValidationModal(false)}
/>
</div>
);
};

export default DAKValidationSection;
74 changes: 11 additions & 63 deletions src/components/Publications.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,21 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, Suspense } from 'react';
import githubService from '../services/githubService';
import StagingGround from './StagingGround';
import DAKPublicationGenerator from './DAKPublicationGenerator';
import { useValidation } from './validation/useValidation';
import { ValidationButton } from './validation/ValidationButton';
import { ValidationReport } from './validation/ValidationReport';
import { ValidationSummary } from './validation/ValidationSummary';

// Lazy load entire validation section to prevent module initialization issues
const DAKValidationSection = React.lazy(() => import('./DAKValidationSection'));

const Publications = ({ profile, repository, selectedBranch, hasWriteAccess }) => {
const [branches, setBranches] = useState([]);
const [workflows, setWorkflows] = useState([]);
const [workflowRuns, setWorkflowRuns] = useState({});
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [showValidationModal, setShowValidationModal] = useState(false);
const [validationComponent, setValidationComponent] = useState('all');

const owner = repository.owner?.login || repository.full_name.split('/')[0];
const repoName = repository.name;

// Validation hook
const { report, loading: validating, validate } = useValidation({
owner,
repo: repoName,
branch: selectedBranch || repository.default_branch
});

useEffect(() => {
const fetchPublicationData = async () => {
try {
Expand Down Expand Up @@ -244,56 +234,14 @@ const Publications = ({ profile, repository, selectedBranch, hasWriteAccess }) =
profile={profile}
/>

{/* DAK Validation Section */}
<div className="dak-validation-section">
<div className="section-header">
<h3 className="section-title">DAK Validation</h3>
<p className="section-description">
Validate DAK artifacts against WHO SMART Guidelines standards. Check BPMN processes,
DMN decision tables, FSH profiles, and DAK-level compliance.
</p>
</div>

<div className="validation-controls">
<div className="component-filter">
<label htmlFor="validation-component-filter">Validate Component:</label>
<select
id="validation-component-filter"
value={validationComponent}
onChange={(e) => setValidationComponent(e.target.value)}
className="component-select"
>
<option value="all">All Components</option>
<option value="business-processes">Business Processes (BPMN)</option>
<option value="decision-logic">Decision Logic (DMN)</option>
<option value="fhir-profiles">FHIR Profiles (FSH)</option>
<option value="dak-config">DAK Configuration</option>
</select>
</div>

<ValidationButton
onClick={() => validate({ component: validationComponent === 'all' ? undefined : validationComponent })}
loading={validating}
status={report ? (report.isValid ? 'success' : (report.summary.errorCount > 0 ? 'error' : 'warning')) : undefined}
label={validating ? 'Validating...' : 'Run Validation'}
/>
</div>

{report && (
<div className="validation-results">
<ValidationSummary
report={report}
onClick={() => setShowValidationModal(true)}
/>
</div>
)}

<ValidationReport
report={report}
isOpen={showValidationModal}
onClose={() => setShowValidationModal(false)}
{/* DAK Validation Section - Lazy loaded to prevent module initialization issues */}
<Suspense fallback={<div style={{ padding: '20px' }}>Loading validation...</div>}>
<DAKValidationSection
owner={owner}
repo={repoName}
branch={selectedBranch || repository.default_branch}
/>
</div>
</Suspense>

<div className="section-header">
<h3 className="section-title">Published DAK Content</h3>
Expand Down
23 changes: 20 additions & 3 deletions src/components/validation/ValidationReport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,18 @@ export const ValidationReport: React.FC<ValidationReportProps> = ({
}, { errorCount: 0, warningCount: 0, infoCount: 0 });

return (
<div className="validation-report-overlay" onClick={onClose}>
<div className="validation-report-modal" onClick={(e) => e.stopPropagation()}>
<div
className="validation-report-overlay"
role="presentation"
>
<div
className="validation-report-modal"
role="dialog"
aria-modal="true"
aria-labelledby="validation-report-title"
>
<div className="validation-report-header">
<h2>Validation Report</h2>
<h2 id="validation-report-title">Validation Report</h2>
<button
className="validation-report-close"
onClick={onClose}
Expand Down Expand Up @@ -213,6 +221,15 @@ export const ValidationReport: React.FC<ValidationReportProps> = ({
<div
className="validation-file-header"
onClick={() => toggleFile(filePath)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleFile(filePath);
}
}}
role="button"
tabIndex={0}
aria-expanded={expandedFiles.has(filePath)}
>
<span className="validation-file-toggle">
{expandedFiles.has(filePath) ? '▼' : '▶'}
Expand Down