# Instructor Portal Consolidation Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Make `instructor/*` the canonical instructor portal while keeping `teacher/*` as a temporary compatibility layer.

**Architecture:** Freeze current behavior with regression tests first, then centralize route decisions and update navigation/auth/Telegram entry points to canonical instructor routes. Keep compatibility redirects where a safe one-to-one mapping exists.

**Tech Stack:** Laravel 12, Livewire, Blade, PHPUnit/Pest-style feature tests, route helpers, existing `InstructorPortalMiddleware`.

---

### Task 1: Add failing regression tests for canonical instructor routing

**Files:**
- Create: `C:\Users\a-djoudi\apps\ecoinv3\tests\Feature\Instructor\InstructorPortalCanonicalRoutesTest.php`
- Read: `C:\Users\a-djoudi\apps\ecoinv3\routes\instructor.php`
- Read: `C:\Users\a-djoudi\apps\ecoinv3\tests\Feature\Teacher\`

- [ ] **Step 1: Write the failing test**

```php
<?php

use App\Models\User;

it('uses instructor dashboard as the canonical instructor landing route', function () {
    $user = User::factory()->create(['role' => 'instructor']);

    $this->actingAs($user)
        ->get(route('instructor.dashboard'))
        ->assertOk();
});

it('redirects teacher dashboard to the canonical instructor dashboard', function () {
    $user = User::factory()->create(['role' => 'instructor']);

    $this->actingAs($user)
        ->get(route('teacher.dashboard'))
        ->assertRedirect(route('instructor.dashboard'));
});
```

- [ ] **Step 2: Run test to verify it fails**

Run: `php artisan test tests/Feature/Instructor/InstructorPortalCanonicalRoutesTest.php`

Expected: at least one failure because `teacher.dashboard` currently renders its own page instead of redirecting.

- [ ] **Step 3: Write minimal implementation**

Update `C:\Users\a-djoudi\apps\ecoinv3\routes\instructor.php` so the `teacher/*` group becomes a compatibility layer for pages with direct `instructor/*` equivalents, starting with dashboard-level routing.

```php
Route::get('/', fn () => redirect()->route('instructor.dashboard'))->name('dashboard');
```

- [ ] **Step 4: Run test to verify it passes**

Run: `php artisan test tests/Feature/Instructor/InstructorPortalCanonicalRoutesTest.php`

Expected: PASS

- [ ] **Step 5: Commit**

```bash
git add routes/instructor.php tests/Feature/Instructor/InstructorPortalCanonicalRoutesTest.php
git commit -m "test: lock canonical instructor dashboard routing"
```

### Task 2: Centralize compatibility mapping for teacher-to-instructor routes

**Files:**
- Create: `C:\Users\a-djoudi\apps\ecoinv3\app\Support\Routing\InstructorRouteMap.php`
- Modify: `C:\Users\a-djoudi\apps\ecoinv3\routes\instructor.php`
- Test: `C:\Users\a-djoudi\apps\ecoinv3\tests\Feature\Instructor\InstructorPortalCanonicalRoutesTest.php`

- [ ] **Step 1: Write the failing test**

Extend the test file with one mapped page beyond dashboard.

```php
it('redirects teacher profile to the canonical instructor profile route', function () {
    $user = User::factory()->create(['role' => 'instructor']);

    $this->actingAs($user)
        ->get(route('teacher.profile'))
        ->assertRedirect(route('instructor.profile'));
});
```

- [ ] **Step 2: Run test to verify it fails**

Run: `php artisan test tests/Feature/Instructor/InstructorPortalCanonicalRoutesTest.php --filter=profile`

Expected: FAIL because `teacher.profile` still resolves to its own destination.

- [ ] **Step 3: Write minimal implementation**

Create a small route map helper and use it from compatibility routes.

```php
<?php

namespace App\Support\Routing;

class InstructorRouteMap
{
    public static function canonicalRouteFor(string $legacyName): ?string
    {
        return [
            'teacher.dashboard' => 'instructor.dashboard',
            'teacher.cohorts.index' => 'instructor.cohorts.index',
            'teacher.cohorts.show' => 'instructor.cohorts.show',
            'teacher.announcements' => 'instructor.announcements',
            'teacher.materials.index' => 'instructor.materials.index',
            'teacher.payouts' => 'instructor.payouts',
            'teacher.profile' => 'instructor.profile',
        ][$legacyName] ?? null;
    }
}
```

- [ ] **Step 4: Run test to verify it passes**

Run: `php artisan test tests/Feature/Instructor/InstructorPortalCanonicalRoutesTest.php`

Expected: PASS

- [ ] **Step 5: Commit**

```bash
git add app/Support/Routing/InstructorRouteMap.php routes/instructor.php tests/Feature/Instructor/InstructorPortalCanonicalRoutesTest.php
git commit -m "refactor: centralize instructor route compatibility map"
```

### Task 3: Update shared navigation to canonical instructor routes

**Files:**
- Modify: `C:\Users\a-djoudi\apps\ecoinv3\resources\views\components\portal\sidebar.blade.php`
- Modify: `C:\Users\a-djoudi\apps\ecoinv3\resources\views\components\public\header.blade.php`
- Test: `C:\Users\a-djoudi\apps\ecoinv3\tests\Feature\Instructor\InstructorPortalNavigationTest.php`

- [ ] **Step 1: Write the failing test**

Create a feature test that renders the relevant navigation for an instructor and asserts canonical links.

```php
<?php

use App\Models\User;

it('renders canonical instructor links in shared navigation', function () {
    $user = User::factory()->create(['role' => 'instructor']);

    $this->actingAs($user)
        ->get(route('instructor.dashboard'))
        ->assertSee(route('instructor.profile'), false)
        ->assertDontSee(route('teacher.profile'), false);
});
```

- [ ] **Step 2: Run test to verify it fails**

Run: `php artisan test tests/Feature/Instructor/InstructorPortalNavigationTest.php`

Expected: FAIL because shared navigation still emits `teacher.*` URLs.

- [ ] **Step 3: Write minimal implementation**

Switch shared navigation route helpers from `teacher.*` to `instructor.*` where the destination is canonical.

```blade
['label' => __('instructor.nav.profile'), 'url' => route('instructor.profile'), 'icon' => 'user', 'active' => request()->routeIs('instructor.profile') || request()->routeIs('teacher.profile')],
```

- [ ] **Step 4: Run test to verify it passes**

Run: `php artisan test tests/Feature/Instructor/InstructorPortalNavigationTest.php`

Expected: PASS

- [ ] **Step 5: Commit**

```bash
git add resources/views/components/portal/sidebar.blade.php resources/views/components/public/header.blade.php tests/Feature/Instructor/InstructorPortalNavigationTest.php
git commit -m "refactor: use canonical instructor links in shared navigation"
```

### Task 4: Update login and Telegram deep links to canonical instructor destinations

**Files:**
- Modify: `C:\Users\a-djoudi\apps\ecoinv3\app\Livewire\Auth\Login.php`
- Modify: `C:\Users\a-djoudi\apps\ecoinv3\app\Http\Controllers\Telegram\TelegramLinkController.php`
- Modify: `C:\Users\a-djoudi\apps\ecoinv3\app\Http\Controllers\Webhooks\TelegramWebhookController.php`
- Test: `C:\Users\a-djoudi\apps\ecoinv3\tests\Feature\Telegram\TelegramInstructorLinksTest.php`
- Test: `C:\Users\a-djoudi\apps\ecoinv3\tests\Feature\Auth\InstructorLoginRedirectTest.php`

- [ ] **Step 1: Write the failing tests**

```php
<?php

use App\Livewire\Auth\Login;
use App\Models\TelegramLinkToken;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Livewire\Livewire;

it('redirects instructor login to the canonical instructor dashboard', function () {
    $user = User::factory()->create([
        'role' => 'instructor',
        'password' => Hash::make('secret-123'),
    ]);

    Livewire::test(Login::class)
        ->set('email', $user->email)
        ->set('password', 'secret-123')
        ->call('login')
        ->assertRedirect(route('instructor.dashboard'));
});

it('renders the telegram link wait page with a canonical instructor return url', function () {
    $user = User::factory()->create(['role' => 'instructor']);
    $token = TelegramLinkToken::query()->create([
        'user_id' => $user->id,
        'token' => Str::lower(Str::random(32)),
        'expires_at' => now()->addMinutes(15),
    ]);

    $this->actingAs($user)
        ->get(route('account.telegram.link.wait', $token))
        ->assertOk()
        ->assertSee(route('instructor.dashboard'), false);
});
```

- [ ] **Step 2: Run tests to verify they fail**

Run: `php artisan test tests/Feature/Auth/InstructorLoginRedirectTest.php tests/Feature/Telegram/TelegramInstructorLinksTest.php`

Expected: FAIL because current code still references `teacher.dashboard` or `teacher.sessions.*`.

- [ ] **Step 3: Write minimal implementation**

Replace old route helpers in auth and Telegram entry points with canonical `instructor.*` routes for dashboard, schedule, and attendance shortcuts where supported.

```php
return redirect()->route('instructor.dashboard');
```

- [ ] **Step 4: Run tests to verify they pass**

Run: `php artisan test tests/Feature/Auth/InstructorLoginRedirectTest.php tests/Feature/Telegram/TelegramInstructorLinksTest.php`

Expected: PASS

- [ ] **Step 5: Commit**

```bash
git add app/Livewire/Auth/Login.php app/Http/Controllers/Telegram/TelegramLinkController.php app/Http/Controllers/Webhooks/TelegramWebhookController.php tests/Feature/Auth/InstructorLoginRedirectTest.php tests/Feature/Telegram/TelegramInstructorLinksTest.php
git commit -m "refactor: canonicalize instructor auth and telegram links"
```

### Task 5: Verify compatibility boundaries and document remaining legacy surface

**Files:**
- Modify: `C:\Users\a-djoudi\apps\ecoinv3\docs\instructor_portal_audit.md`
- Modify: `C:\Users\a-djoudi\apps\ecoinv3\docs\refactoring_roadmap.md`
- Test: `C:\Users\a-djoudi\apps\ecoinv3\tests\Feature\Instructor\InstructorPortalCanonicalRoutesTest.php`
- Test: `C:\Users\a-djoudi\apps\ecoinv3\tests\Feature\Instructor\InstructorPortalNavigationTest.php`

- [ ] **Step 1: Write the failing verification step**

List the still-unmigrated legacy endpoints discovered during implementation in the docs update before declaring the phase done.

```md
- Legacy teacher attendance routes still retained temporarily because they do not yet have one-to-one parity.
```

- [ ] **Step 2: Run the focused test suite**

Run: `php artisan test tests/Feature/Instructor/InstructorPortalCanonicalRoutesTest.php tests/Feature/Instructor/InstructorPortalNavigationTest.php tests/Feature/Auth/InstructorLoginRedirectTest.php tests/Feature/Telegram/TelegramInstructorLinksTest.php`

Expected: PASS

- [ ] **Step 3: Write minimal documentation updates**

Update both docs with:

```md
## Phase Status

- Canonical portal namespace: `instructor.*`
- Compatibility namespace: `teacher.*` redirects kept temporarily
- Remaining legacy surface: attendance-specific teacher pages pending phase two
```

- [ ] **Step 4: Re-run the focused test suite**

Run: `php artisan test tests/Feature/Instructor/InstructorPortalCanonicalRoutesTest.php tests/Feature/Instructor/InstructorPortalNavigationTest.php tests/Feature/Auth/InstructorLoginRedirectTest.php tests/Feature/Telegram/TelegramInstructorLinksTest.php`

Expected: PASS

- [ ] **Step 5: Commit**

```bash
git add docs/instructor_portal_audit.md docs/refactoring_roadmap.md
git commit -m "docs: record canonical instructor portal phase status"
```
