Files
OpenNoodl/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-001D-legacy-readonly-enforcement/PHASE-3-COMPLETE.md
Richard Osborne ddcb9cd02e feat: Phase 5 BYOB foundation + Phase 3 GitHub integration
Phase 5 - BYOB Backend (TASK-007A/B):
- LocalSQL Adapter with full CloudStore API compatibility
- QueryBuilder translates Parse-style queries to SQL
- SchemaManager with PostgreSQL/Supabase export
- LocalBackendServer with REST endpoints
- BackendManager with IPC handlers for Electron
- In-memory fallback when better-sqlite3 unavailable

Phase 3 - GitHub Panel (GIT-004):
- Issues tab with list/detail views
- Pull Requests tab with list/detail views
- GitHub API client with OAuth support
- Repository info hook integration

Phase 3 - Editor UX Bugfixes (TASK-013):
- Legacy runtime detection banners
- Read-only enforcement for legacy projects
- Code editor modal close improvements
- Property panel stuck state fix
- Blockly node deletion and UI polish

Phase 11 - Cloud Functions Planning:
- Architecture documentation for workflow automation
- Execution history storage schema design
- Canvas overlay concept for debugging

Docs: Updated LEARNINGS.md and COMMON-ISSUES.md
2026-01-15 17:37:15 +01:00

5.7 KiB

TASK-001D Phase 3 Complete: Read-Only Enforcement

Date: 2026-01-13
Status: Complete

What Was Fixed

Critical Bug: Read-Only Mode Was Not Actually Enforcing!

When clicking "Open Read-Only" on a legacy project, the code was calling the right function but never actually passing the readOnly flag through the routing system. The project would open normally and be fully editable.

The Complete Fix

1. Added readOnly to Routing Interface

File: packages/noodl-editor/src/editor/src/pages/AppRouter.ts

export interface AppRouteOptions {
  to: string;
  from?: string;
  uri?: string;
  project?: ProjectModel;
  readOnly?: boolean; // NEW: Flag to open project in read-only mode
}

2. Added _isReadOnly Property to ProjectModel

File: packages/noodl-editor/src/editor/src/models/projectmodel.ts

export class ProjectModel extends Model {
  public _retainedProjectDirectory?: string;
  public _isReadOnly?: boolean; // NEW: Flag for read-only mode (legacy projects)
  public settings?: ProjectSettings;
  // ...
}

3. Router Passes readOnly Flag and Sets on ProjectModel

File: packages/noodl-editor/src/editor/src/router.tsx

if (args.project && ProjectModel.instance !== args.project) {
  ProjectModel.instance = args.project;

  // Set read-only mode if specified (for legacy projects)
  if (args.readOnly !== undefined) {
    args.project._isReadOnly = args.readOnly;
  }
}

// Routes
if (args.to === 'editor') {
  this.setState({
    route: EditorPage,
    routeArgs: { route, readOnly: args.readOnly } // Pass through
  });
}

4. ProjectsPage Passes readOnly: true When Opening Legacy Projects

File: packages/noodl-editor/src/editor/src/pages/ProjectsPage/ProjectsPage.tsx

const handleOpenReadOnly = useCallback(
  async (projectId: string) => {
    // ... load project ...

    tracker.track('Legacy Project Opened Read-Only', {
      projectName: project.name
    });

    // Open the project in read-only mode
    props.route.router.route({ to: 'editor', project: loaded, readOnly: true });
  },
  [props.route]
);

5. NodeGraphContext Detects and Applies Read-Only Mode

File: packages/noodl-editor/src/editor/src/contexts/NodeGraphContext/NodeGraphContext.tsx

// Detect and apply read-only mode from ProjectModel
useEffect(() => {
  if (!nodeGraph) return;

  const eventGroup = {};

  // Apply read-only mode when project instance changes
  const updateReadOnlyMode = () => {
    const isReadOnly = ProjectModel.instance?._isReadOnly || false;
    nodeGraph.setReadOnly(isReadOnly);
  };

  // Listen for project changes
  EventDispatcher.instance.on('ProjectModel.instanceHasChanged', updateReadOnlyMode, eventGroup);

  // Apply immediately if project is already loaded
  updateReadOnlyMode();

  return () => {
    EventDispatcher.instance.off(eventGroup);
  };
}, [nodeGraph]);

The Complete Flow

  1. User clicks "Open Read-Only" on legacy project card
  2. ProjectsPage.handleOpenReadOnly() loads project and calls:
    props.route.router.route({ to: 'editor', project: loaded, readOnly: true });
    
  3. Router.route() receives readOnly: true and:
    • Sets ProjectModel.instance._isReadOnly = true
    • Passes readOnly: true to EditorPage
  4. EventDispatcher fires 'ProjectModel.instanceHasChanged' event
  5. NodeGraphContext hears the event and:
    • Checks ProjectModel.instance._isReadOnly
    • Calls nodeGraph.setReadOnly(true)
  6. NodeGraphEditor.setReadOnly() (already implemented):
    • Sets this.readOnly = true
    • Calls this.renderEditorBanner() to show warning banner
    • Banner appears with "Migrate Now" and "Learn More" buttons
  7. Existing readOnly checks throughout NodeGraphEditor prevent:
    • Adding/deleting nodes
    • Creating/removing connections
    • Editing properties
    • Copy/paste/cut operations
    • Undo/redo

Files Modified

  1. packages/noodl-editor/src/editor/src/pages/AppRouter.ts - Added readOnly to interface
  2. packages/noodl-editor/src/editor/src/models/projectmodel.ts - Added _isReadOnly property
  3. packages/noodl-editor/src/editor/src/router.tsx - Pass and apply readOnly flag
  4. packages/noodl-editor/src/editor/src/pages/ProjectsPage/ProjectsPage.tsx - Pass readOnly=true
  5. packages/noodl-editor/src/editor/src/contexts/NodeGraphContext/NodeGraphContext.tsx - Detect and apply

Testing Required

Before marking completely done, test with a legacy React 17 project:

  1. Open legacy project → see alert
  2. Choose "Open Read-Only"
  3. Banner should appear at top of editor
  4. Editing should be blocked (cannot add nodes, make connections, etc.)
  5. Close project, return to launcher
  6. Legacy badge should still show on project card
  7. Restart editor
  8. Legacy badge should persist (runtime info cached)

Next Steps

Phase 4: Wire "Migrate Now" Button

Currently shows placeholder toast. Need to:

  • Import and render MigrationWizard dialog
  • Pass project path and name
  • Handle completion/cancellation
  • Refresh project list after migration

Phase 5: Runtime Info Persistence

The runtime detection works but results aren't saved to disk, so:

  • Detection re-runs every time
  • Badge disappears after closing project
  • Need to persist runtime info in project metadata or local storage

Notes

  • The existing readOnly checks in NodeGraphEditor already block most operations
  • The banner system from Phase 2 works perfectly
  • The routing system cleanly passes the flag through all layers
  • EventDispatcher pattern ensures NodeGraphContext stays in sync with ProjectModel
  • No breaking changes - readOnly is optional everywhere