This project needs migration to work with OpenNoodl 1.2+. Your original project will remain untouched.
{
e.stopPropagation();
onMigrateProject?.();
}}
/>
{
e.stopPropagation();
onOpenReadOnly?.();
}}
/>
{
e.stopPropagation();
// Open documentation
window.open('https://docs.opennoodl.com/migration', '_blank');
}}
/>
)}
);
}
```
### 2. `LauncherProjectCard.module.scss`
**Location**: `packages/noodl-core-ui/src/preview/launcher/Launcher/components/LauncherProjectCard/LauncherProjectCard.module.scss`
**Add these styles**:
```scss
.LegacyCard {
border-color: var(--theme-color-border-danger) !important;
&:hover {
border-color: var(--theme-color-border-danger-hover) !important;
}
}
.LegacyBanner {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-2);
margin-top: var(--spacing-3);
padding: var(--spacing-2) var(--spacing-3);
background-color: var(--theme-color-bg-danger-subtle);
border: 1px solid var(--theme-color-border-danger);
border-radius: var(--border-radius-medium);
}
.LegacyDetails {
margin-top: var(--spacing-2);
padding: var(--spacing-3);
background-color: var(--theme-color-bg-2);
border-radius: var(--border-radius-medium);
border: 1px solid var(--theme-color-border-default);
}
```
### 3. `Projects.tsx`
**Location**: `packages/noodl-core-ui/src/preview/launcher/Launcher/views/Projects.tsx`
**Changes**:
Pass runtime info and callbacks to each `LauncherProjectCard`:
```typescript
import { LocalProjectsModel } from '@noodl-utils/LocalProjectsModel';
// Add import
function Projects() {
const {
projects,
selectedFolder,
searchQuery,
onMigrateProject, // NEW: From context
onOpenProjectReadOnly // NEW: From context
} = useLauncherContext();
// Get projects with runtime info
const projectsWithRuntime = LocalProjectsModel.instance.getProjectsWithRuntime();
// Filter projects based on folder and search
const filteredProjects = projectsWithRuntime
.filter((project) => {
if (selectedFolder && selectedFolder !== 'all') {
const meta = getProjectMeta(project.localPath);
return meta?.folderId === selectedFolder;
}
return true;
})
.filter((project) => {
if (!searchQuery) return true;
return project.title.toLowerCase().includes(searchQuery.toLowerCase());
});
return (
{filteredProjects.map((project) => (
onMigrateProject(project)} // NEW
onOpenReadOnly={() => onOpenProjectReadOnly(project)} // NEW
contextMenuItems={
[
// ... existing context menu items ...
]
}
/>
))}
);
}
```
## Types to Add
Create a new types file for migration if it doesn't exist:
**File**: `packages/noodl-types/src/migration.ts`
```typescript
export interface RuntimeVersionInfo {
version: 'react17' | 'react19' | 'unknown';
confidence: 'high' | 'medium' | 'low';
indicators: string[];
}
```
## Testing Checklist
- [ ] Legacy projects show warning icon in title
- [ ] Legacy projects have orange/red border
- [ ] Legacy banner shows "React 17 (Legacy Runtime)"
- [ ] Clicking "More" expands details section
- [ ] Clicking "Less" collapses details section
- [ ] "Migrate Project" button is visible
- [ ] "View Read-Only" button is visible
- [ ] "Learn More" button is visible
- [ ] Normal projects don't show any legacy indicators
- [ ] Detection spinner shows while runtime is being detected
- [ ] Clicking card body for legacy projects doesn't trigger onClick
## Visual Design
```
┌────────────────────────────────────────────────────────────┐
│ [Thumbnail] My Legacy Project ⚠️ │
│ Last opened 2 days ago │
│ │
│ ┌────────────────────────────────────────────┐│
│ │ ⚠️ React 17 (Legacy Runtime) [More ▼]││
│ └────────────────────────────────────────────┘│
│ │
│ ┌────────────────────────────────────────────┐│
│ │ This project needs migration to work with ││
│ │ OpenNoodl 1.2+. Your original project will ││
│ │ remain untouched. ││
│ │ ││
│ │ [Migrate Project] [View Read-Only] Learn More → ││
│ └────────────────────────────────────────────┘│
└────────────────────────────────────────────────────────────┘
```
## Notes
- **Non-blocking**: Normal click behavior is disabled for legacy projects to prevent accidental opening
- **Informative**: Clear warning with explanation
- **Actionable**: Three clear paths forward (migrate, view, learn)
- **Expandable**: Details hidden by default to avoid clutter
- **Color coding**: Use danger colors to indicate incompatibility without being alarming
## Next Steps
After completing this subtask:
1. Verify legacy badges appear correctly
2. Test expand/collapse behavior
3. Move to Subtask B to wire up the button callbacks