# Implementation Plan: Go No Go

Bare Laravel 12 + Nova 5 install to production-ready questionnaire application.

**Current state:** Laravel framework and Nova installed (Nova auth already working). No Inertia, Vue, Socialite, or application code.

**Reference docs:** `docs/technical-requirements.md`, `docs/theming-templating-vue.md`, `docs/questions-*.md`

---

## Step 1: Install Dependencies and Configure Build

[x] **Install and configure the frontend toolchain and auth packages.**

Install Vue 3, Inertia.js v2 (server-side adapter + client-side `@inertiajs/vue3`), Tailwind CSS 4 with design tokens from `docs/theming-templating-vue.md`, Heroicons, and Laravel Socialite. Configure `vite.config.js` for Vue + Inertia. Configure `tailwind.config.js` with the project color tokens (primary `#d1ec51`, secondary `#00b7b3`, surface `#2b303a`). Register the `HandleInertiaRequests` middleware. Create the Inertia root Blade template (`app.blade.php`).

**Creates:**
- `vite.config.js` -- Vue plugin + Inertia page resolver
- `tailwind.config.js` -- custom color tokens
- `resources/views/app.blade.php` -- Inertia root template with `@vite` and `@inertiaHead`
- `resources/js/app.js` -- Vue + Inertia app bootstrap
- `app/Http/Middleware/HandleInertiaRequests.php`
- `composer.json` updates: `inertiajs/inertia-laravel`, `laravel/socialite`
- `package.json` updates: `vue`, `@inertiajs/vue3`, `@vitejs/plugin-vue`, `@heroicons/vue`, `tailwindcss`

**Validates:** `npm run build` completes without errors. Visiting any route renders the Inertia root template without a blank page or console errors.

**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test`
2. `browser_snapshot` -- confirm the page renders content (not a blank page or Vite error screen)
3. `browser_console_messages` with level `error` -- verify no JavaScript console errors
4. `browser_take_screenshot` -- save as `step-01-homepage.png` for visual confirmation
5. If a default Laravel welcome or Inertia root page loads, the step passes

---

## Step 2: Database -- Migrations and Models

[x] **Create all database tables and Eloquent models.**

Create migrations for: `categories`, `question_groups`, `questions`, `screenings`, `screening_answers`, `sessions`, `answers`, `logs`. Create corresponding Eloquent models with relationships, `$fillable`, `$casts`, and any unique constraints (e.g., `answers` has a unique composite on `session_id + question_id`, `screening_answers` has a unique composite on `screening_id + question_number`). The `logs` table has no `updated_at` column (append-only). The `sessions` table has a nullable `screening_id` FK linking back to the screening that authorized it. See `docs/technical-requirements.md` section 5 for exact column definitions.

**Creates:**
- `database/migrations/*_create_categories_table.php`
- `database/migrations/*_create_question_groups_table.php`
- `database/migrations/*_create_questions_table.php`
- `database/migrations/*_create_screenings_table.php`
- `database/migrations/*_create_screening_answers_table.php`
- `database/migrations/*_create_sessions_table.php`
- `database/migrations/*_create_answers_table.php`
- `database/migrations/*_create_logs_table.php`
- `app/Models/Category.php`
- `app/Models/QuestionGroup.php`
- `app/Models/Question.php`
- `app/Models/Screening.php`
- `app/Models/ScreeningAnswer.php`
- `app/Models/Session.php`
- `app/Models/Answer.php`
- `app/Models/Log.php`

**Validates:** `herd php artisan migrate:fresh` runs cleanly. All models load without errors. `herd php artisan app:schema-generate` produces `database/schema.md` matching the spec.

**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test`
2. `browser_snapshot` -- confirm the app still loads without database connection errors or migration failures
3. `browser_console_messages` with level `error` -- verify no errors
4. No UI to test yet; this step is purely backend. Passing means the app does not crash after migrations.

---

## Step 3: Authentication

[x] **Set up Socialite for frontend SSO.**

Nova already handles its own admin authentication at `/cp` (built-in). Configure Socialite with the Azure AD driver for frontend SSO. Create `SocialiteController` with `redirect`, `callback`, and `logout` methods. The callback creates or matches users by email. Add the `/login-jonathan` dev auto-login route (local/testing environments only).

**Creates:**
- `app/Http/Controllers/Auth/SocialiteController.php`
- `config/services.php` Azure AD configuration
- Route registrations in `routes/web.php` for `/login`, `/auth/callback`, `/logout`, `/login-jonathan`

**Validates:** Visiting `/login` redirects to Azure AD (or errors with missing credentials, confirming the redirect is wired). Nova login at `/cp` continues to work as before. `/login-jonathan` logs in and redirects to `/` in local environment.

**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. `browser_snapshot` -- confirm redirect to `/` after auto-login (page should show authenticated state)
3. `browser_take_screenshot` -- save as `step-03-login-jonathan.png`
4. `browser_navigate` to `http://go-no-go.test/cp`
5. `browser_snapshot` -- confirm Nova login page loads (should show email/password form)
6. `browser_take_screenshot` -- save as `step-03-nova-login.png`
7. `browser_console_messages` with level `error` -- verify no errors on either page

---

## Step 4: Frontend Layout and Shared Components

[x] **Build the persistent layout and all shared Vue components.**

Create `AppLayout.vue` as a persistent Inertia layout with `PageHeader` containing the Piccadilly logo and page title. Create shared components: `AppLogo`, `PageHeader`, `AppButton` (with variant/size/href/disabled/loading props per `docs/theming-templating-vue.md`), `ScoreIndicator` (color-coded by threshold). Wire up `HandleInertiaRequests` shared data (authenticated user, flash messages). Add the `EncryptHistory` middleware for sensitive session data.

**Creates:**
- `resources/js/Layouts/AppLayout.vue`
- `resources/js/Components/AppLogo.vue`
- `resources/js/Components/PageHeader.vue`
- `resources/js/Components/AppButton.vue`
- `resources/js/Components/ScoreIndicator.vue`
- Shared data configuration in `HandleInertiaRequests.php`
- `EncryptHistory` middleware registration

**Validates:** A test page using `AppLayout` renders the header with logo and title. `AppButton` renders all three variants. Shared data (`$page.props.auth.user`) is accessible in any page component.

**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test/login-jonathan` (authenticate first)
2. `browser_navigate` to `http://go-no-go.test/` (or any test page using AppLayout)
3. `browser_snapshot` -- confirm the layout renders: look for the Piccadilly logo in the header and the page title
4. `browser_take_screenshot` -- save as `step-04-layout.png` to visually verify header, logo, and overall layout structure
5. Verify the snapshot contains recognizable layout elements (header, logo component, page content area)
6. `browser_console_messages` with level `error` -- verify no Vue rendering errors

---

## Step 5: Page Stubs and Click-Through Flow

[x] **Create stub pages and controllers so the full two-stage navigation flow is clickable end to end.**

Create stub Vue page components for the entire flow: `Pages/Landing.vue` (intro text + Continue button), `Pages/Screening/Show.vue` (placeholder for pre-screening questions), `Pages/Screening/Result.vue` (placeholder pass/fail + category picker), `Pages/Session/Show.vue` (placeholder for basic info + questionnaire), `Pages/Session/Result.vue` (placeholder final result + Again button). Create stub controllers: `LandingController@index`, `ScreeningController` with `store`, `show`, `update`, `result` methods, `SessionController` with `store`, `show`, `update`, `result` methods. Wire up all routes in `web.php` matching the route table in `docs/technical-requirements.md` section 6.

This step is intentionally minimal -- no real data, no real forms. The goal is a complete clickable flow: Landing → Screening → Screening Result (with hardcoded category list) → Session/Show → Session/Result → back to Landing.

**Creates:**
- `resources/js/Pages/Landing.vue`
- `resources/js/Pages/Screening/Show.vue`
- `resources/js/Pages/Screening/Result.vue`
- `resources/js/Pages/Session/Show.vue`
- `resources/js/Pages/Session/Result.vue`
- `app/Http/Controllers/LandingController.php`
- `app/Http/Controllers/ScreeningController.php`
- `app/Http/Controllers/SessionController.php`
- All frontend route definitions in `routes/web.php`

**Validates:** Log in via `/login-jonathan`. See the landing page with Continue button. Click Continue to reach pre-screening. Click through to screening result. Select a category to start a session. Navigate through to the final result page. Click "Again" to return to landing. All page transitions work without errors. Browser back/forward works.

**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test/login-jonathan` -- authenticate
2. `browser_snapshot` -- confirm landing page with Continue button
3. `browser_take_screenshot` -- save as `step-05-landing.png`
4. `browser_click` the Continue button
5. `browser_snapshot` -- confirm screening page loads (pre-screening stub)
6. `browser_take_screenshot` -- save as `step-05-screening.png`
7. Click through to the screening result page (click submit/continue on the screening stub)
8. `browser_snapshot` -- confirm screening result page with category list
9. `browser_take_screenshot` -- save as `step-05-screening-result.png`
10. `browser_click` a category "Start" button to begin a session
11. `browser_snapshot` -- confirm session/show page loads
12. `browser_take_screenshot` -- save as `step-05-session-show.png`
13. Click through to the session result page
14. `browser_snapshot` -- confirm result page with "Again" button
15. `browser_take_screenshot` -- save as `step-05-session-result.png`
16. `browser_click` the "Again" button
17. `browser_snapshot` -- confirm return to the landing page
18. `browser_navigate_back` -- verify browser back navigation works without errors
19. `browser_console_messages` with level `error` -- verify no errors throughout the entire flow

---

## Step 6: Seeders -- Categories, Question Groups, and Questions

[x] **Seed all reference data: pre-screening questions, 6 categories, their question groups, and all questions with correct field configuration.**

Create `DatabaseSeeder` (or dedicated seeders) for: the 10 pre-screening Yes/No questions (stored in config or a seeder — these are not in the `questions` table, they are handled by the screening flow), the 6 categories (Audit, Outsource, Solution, Digital Solutions, Legal, Tax) with correct `sort_order`, all question groups per category with names, descriptions, and `scoring_instructions` where applicable, and all questions with the correct `has_yes`, `has_no`, `has_na`, `details`, `is_scored`, and `sort_order` values. Source question data from `docs/questions-audit.md`, `docs/questions-outsource-solutions.md`, `docs/questions-digital-solutions.md`, `docs/questions-legal.md`, `docs/questions-tax.md`.

**Creates:**
- `database/seeders/DatabaseSeeder.php`
- `database/seeders/CategorySeeder.php` (or inline)
- `database/seeders/QuestionSeeder.php` (or per-category seeders)
- Pre-screening question definitions (config file or seeder)

**Validates:** `herd php artisan migrate:fresh --seed` runs cleanly. Verify category count is 6. Verify each category has the expected number of question groups. Spot-check several questions to confirm field configuration matches the source docs. Verify 10 pre-screening questions are available.

**Browser Verification (Playwright MCP):**
1. After running `herd php artisan migrate:fresh --seed`, `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. Navigate through the stub flow to verify seeded data appears:
   - `browser_navigate` to the screening result page -- verify the 6 category names appear (Audit, Outsource, Solution, Digital Solutions, Legal, Tax)
   - `browser_take_screenshot` -- save as `step-06-categories.png`
3. Start a session for any category, navigate to the screening page -- verify seeded pre-screening questions render
4. `browser_take_screenshot` -- save as `step-06-screening-questions.png`
5. `browser_snapshot` -- confirm question text is present and not placeholder content
6. `browser_console_messages` with level `error` -- verify no errors

---

## Step 7: Landing Page and Pre-Screening Flow

[x] **Build the real landing page and pre-screening questionnaire.**

Build the Landing page: describes the application purpose (what Go/No Go is, what the user will do), with a "Continue" button that creates a screening and redirects to the pre-screening questions. Build the Screening/Show page: render the 10 Yes/No pre-screening questions using `useForm`. Save screening answers via `PUT /screening/{screening}`. On submit, calculate the screening score (Yes=1 point each), determine pass/fail (>=5 = pass), and redirect to the screening result page. Build Screening/Result: if failed (<5 points), show No Go result with "Again" button back to `/`. If passed, show the category picker (list of 6 categories) with "Start" buttons. Selecting a category creates a session linked to this screening and redirects to Session/Show.

**Creates:**
- Updated `resources/js/Pages/Landing.vue` -- application description + Continue button
- Updated `resources/js/Pages/Screening/Show.vue` -- 10 Yes/No questions with useForm
- Updated `resources/js/Pages/Screening/Result.vue` -- pass/fail display + category picker on pass
- Updated `app/Http/Controllers/LandingController.php`
- Updated `app/Http/Controllers/ScreeningController.php` -- full store/show/update/result logic
- Screening scoring logic

**Validates:** Landing page renders with description and Continue button. Clicking Continue creates a screening and shows 10 questions. Answering all No (score 0) → No Go result. Answering 5+ Yes → pass, category picker shown. Selecting a category creates a session and navigates to Session/Show.

**Browser Verification (Playwright MCP):**

*Test path 1 -- No Go (all No answers):*
1. `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. `browser_snapshot` -- confirm landing page with description text and Continue button
3. `browser_take_screenshot` -- save as `step-07-landing.png`
4. `browser_click` the Continue button
5. `browser_snapshot` -- confirm 10 pre-screening Yes/No questions render
6. `browser_take_screenshot` -- save as `step-07-screening-questions.png`
7. `browser_fill_form` -- select "No" for all 10 questions
8. `browser_click` the Submit button
9. `browser_snapshot` -- confirm No Go result page (score <5)
10. `browser_take_screenshot` -- save as `step-07-nogo-result.png`
11. Verify the page shows a No Go message and an "Again" button (no category picker)

*Test path 2 -- Pass (5+ Yes answers):*
12. `browser_navigate` to `http://go-no-go.test/` -- start fresh
13. `browser_click` the Continue button
14. `browser_fill_form` -- select "Yes" for at least 5 questions (e.g., first 6 Yes, rest No)
15. `browser_click` the Submit button
16. `browser_snapshot` -- confirm pass result with category picker showing 6 categories
17. `browser_take_screenshot` -- save as `step-07-pass-result.png`
18. `browser_click` a category "Start" button
19. `browser_snapshot` -- confirm redirect to Session/Show page
20. `browser_take_screenshot` -- save as `step-07-session-started.png`
21. `browser_console_messages` with level `error` -- verify no errors across both paths

---

## Step 8: Basic Info Form

[x] **Build the basic info step as the first section of Session/Show.**

Add the basic info form fields to the Session/Show page: `client_name`, `client_contact`, `lead_firm_name`, `lead_firm_contact`. Use Inertia `useForm` to save data to the session's `basic_info` JSON column via `PUT /sessions/{session}`. All four fields are required before the user can proceed to questions. Display validation errors inline.

**Creates:**
- Updated `resources/js/Pages/Session/Show.vue` -- basic info form section
- Updated `app/Http/Controllers/SessionController.php` -- handle basic info save in `update` method
- Form validation rules for basic info fields

**Validates:** Starting a new session shows the basic info form. Submitting with empty fields shows validation errors. Filling all fields and saving persists to the `basic_info` JSON column. Refreshing the page retains the saved values.

**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. Complete the pre-screening flow (5+ Yes) and select a category to start a session
3. `browser_snapshot` -- confirm basic info form renders with 4 fields (client_name, client_contact, lead_firm_name, lead_firm_contact)
4. `browser_take_screenshot` -- save as `step-08-basic-info-empty.png`

*Test validation errors:*
5. `browser_click` the Save/Submit button without filling any fields
6. `browser_snapshot` -- confirm validation error messages appear for all 4 required fields
7. `browser_take_screenshot` -- save as `step-08-validation-errors.png`

*Test successful submission:*
8. `browser_fill_form` -- fill all 4 fields with test data:
   - client_name: "Test Client Ltd"
   - client_contact: "Jane Doe"
   - lead_firm_name: "Lead Firm BV"
   - lead_firm_contact: "John Smith"
9. `browser_click` the Save button
10. `browser_snapshot` -- confirm save succeeded (no validation errors)
11. `browser_take_screenshot` -- save as `step-08-basic-info-filled.png`

*Test persistence:*
12. `browser_navigate` to the same session URL (refresh the page)
13. `browser_snapshot` -- confirm all 4 fields still contain the previously saved values
14. `browser_take_screenshot` -- save as `step-08-basic-info-retained.png`
15. `browser_console_messages` with level `error` -- verify no errors

---

## Step 9: Questionnaire Flow -- Question Rendering and Answer Saving

[x] **Build the full questionnaire UI with all 6 question patterns and answer persistence.**

Create the `QuestionCard` component that renders questions based on their field configuration (see the 6 patterns in `docs/technical-requirements.md` section 5). Render all questions on a single scrollable page within `Session/Show` (not paginated per group). In phase 2, questions will be visually grouped by their question group with group headers and scoring instructions. Save answers via `PUT /sessions/{session}` using Inertia `useForm` with partial reloads (only reload answers/score, not the full question set). Handle `details` textarea visibility: show when `details` is `required` or `optional`; show conditionally for `req_on_yes` / `req_on_no` based on the selected value. Include the Additional Comments textarea at the bottom of the page.

**Creates:**
- `resources/js/Components/QuestionCard.vue`
- Updated `resources/js/Pages/Session/Show.vue` -- single-page question rendering
- Updated `app/Http/Controllers/SessionController.php` -- answer saving logic in `update`
- Answer validation rules

**Validates:** All questions for the category render on a single scrollable page. Each question renders the correct UI pattern (radio buttons, text-only, details visibility). Answers save without page reload. Refreshing the page retains saved answers. All 6 question patterns render correctly. Additional Comments textarea appears at the bottom.

**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. Complete pre-screening (5+ Yes), select a category known to have diverse question types (e.g., Audit), fill basic info

*Test single-page question rendering:*
3. `browser_snapshot` -- confirm all questions render on a single page
4. `browser_take_screenshot` -- save as `step-09-questions-full-page.png`

*Test all 6 question patterns (scroll through to find each):*
5. For each question pattern encountered:
   - `browser_snapshot` -- verify correct UI elements (radio buttons for Yes/No/NA, details textarea visibility)
   - `browser_take_screenshot` -- save as `step-09-pattern-{N}.png` for each distinct pattern
6. Verify details field behavior:
   - Find a question with `details: req_on_yes` -- select Yes, confirm details textarea appears
   - Select No on the same question -- confirm details textarea hides
   - `browser_take_screenshot` -- save as `step-09-conditional-details.png`

*Test answer saving:*
7. `browser_fill_form` -- answer several questions on the page
8. Refresh the page
9. `browser_snapshot` -- confirm previously saved answers are still selected
10. `browser_take_screenshot` -- save as `step-09-answers-retained.png`

*Test Additional Comments section:*
11. Scroll to the bottom of the page
12. `browser_snapshot` -- confirm Additional Comments textarea renders at the bottom
13. `browser_take_screenshot` -- save as `step-09-additional-comments.png`
14. `browser_console_messages` with level `error` -- verify no errors

---

## Step 10: Scoring and Result

[x] **Implement server-side scoring and the result page.**

Calculate the score server-side from `is_scored` answers: Yes=1, No=0, NA=excluded. Return the running score and current result threshold via Inertia props during the questionnaire. Build the `ScoreIndicator` into the questionnaire flow with live color updates (green 10+, yellow 5-9, red 1-4). Build the real `Session/Result` page showing the final GO / NO GO / Consult Leadership result with color coding and an "Again" button that returns to `/`. On session submission, calculate final score and persist `score`, `result`, `status=completed`, and `completed_at` to the session.

**Creates:**
- Scoring logic (service class or model method)
- Updated `app/Http/Controllers/SessionController.php` -- scoring in `update` (completion) and `result`
- Updated `resources/js/Pages/Session/Show.vue` -- ScoreIndicator integration
- Updated `resources/js/Pages/Session/Result.vue` -- final result display
- Updated `resources/js/Components/ScoreIndicator.vue` -- live threshold colors

**Validates:** Complete a session with known answers and verify the score matches manual calculation. ScoreIndicator changes color as answers are saved. Result page displays the correct GO/NO GO/Consult result. Session record in the database has correct `score`, `result`, and `completed_at`.

**Browser Verification (Playwright MCP):**
1. `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. Complete pre-screening, select a category, fill basic info

*Test ScoreIndicator live updates:*
3. Answer the first few scored questions with "Yes" (score builds up)
4. `browser_snapshot` -- confirm ScoreIndicator is visible and shows current score
5. `browser_take_screenshot` -- save as `step-10-score-low.png` (should be red, score 1-4)
6. Continue answering "Yes" to build score to 5-9 range
7. `browser_take_screenshot` -- save as `step-10-score-mid.png` (should be yellow)
8. Continue answering "Yes" to push score above 10
9. `browser_take_screenshot` -- save as `step-10-score-high.png` (should be green)

*Test session submission and result:*
10. Complete all remaining questions and navigate to the final step
11. `browser_click` the Submit/Complete button
12. `browser_snapshot` -- confirm result page loads with GO / NO GO / Consult Leadership verdict
13. `browser_take_screenshot` -- save as `step-10-result-page.png`
14. Verify the result page shows:
    - The correct result text (GO, NO GO, or Consult Leadership) based on the score
    - Appropriate color coding matching the result
    - An "Again" button
15. `browser_click` the "Again" button
16. `browser_snapshot` -- confirm return to the landing page
17. `browser_console_messages` with level `error` -- verify no errors

---

## Step 11: Activity Logging

[x] **Add append-only activity logging for analytics.**

Create a logging service (or helper) that writes to the `logs` table. Integrate log writes into all relevant actions: `login`, `logout`, `screening_started`, `screening_completed`, `session_started`, `session_completed`, `session_abandoned`, `answer_saved`, `step_viewed`. Each log includes `user_id`, `session_id`, `category_id`, `action`, and `metadata` JSON as defined in `docs/technical-requirements.md` section 5 (logs table). The Log model should have no `updated_at` and should prevent updates/deletes.

**Creates:**
- `app/Services/ActivityLogger.php` (or similar)
- Log write calls in `SocialiteController`, `SessionController`, and relevant middleware/listeners
- Updated `app/Models/Log.php` -- guard against updates/deletes

**Validates:** Perform a full user flow (login, start session, answer questions, view steps, complete session, logout). Query the `logs` table and verify all expected actions are recorded with correct metadata.

**Browser Verification (Playwright MCP):**

*Step 1 -- Perform a full flow to generate log entries:*
1. `browser_navigate` to `http://go-no-go.test/login-jonathan` -- triggers `login` log
2. Complete pre-screening (triggers `screening_started`, `screening_completed`)
3. Select a category and start a session (triggers `session_started`)
4. Answer several questions (triggers `answer_saved`, `step_viewed`)
5. Complete the session (triggers `session_completed`)

*Step 2 -- Verify logs in Nova admin:*
6. `browser_navigate` to `http://go-no-go.test/cp`
7. `browser_fill_form` -- log in with Nova admin credentials (email: jonathan@blijnder.nl, password: secret)
8. `browser_snapshot` -- confirm Nova dashboard loads
9. `browser_click` the "Logs" item in the Nova sidebar
10. `browser_snapshot` -- confirm log entries are listed with action types, user IDs, and timestamps
11. `browser_take_screenshot` -- save as `step-11-nova-logs-index.png`
12. `browser_click` a specific log entry to view its detail
13. `browser_snapshot` -- confirm log detail shows action, user_id, session_id, category_id, and metadata JSON
14. `browser_take_screenshot` -- save as `step-11-nova-log-detail.png`
15. Verify that log entries include: `login`, `screening_started`, `screening_completed`, `session_started`, `answer_saved`, `step_viewed`, `session_completed`
16. `browser_console_messages` with level `error` -- verify no errors

---

## Step 12: Nova Resources and Policies

[x] **Create all Nova resources, policies, and Excel export actions.**

Create Nova resources: `CategoryResource`, `QuestionGroupResource`, `QuestionResource`, `ScreeningResource`, `SessionResource`, `AnswerResource`, `LogResource`. Create corresponding policies enforcing the permission matrix from `docs/technical-requirements.md` section 9 (most are read-only; only Question.text is editable). Apply field behaviors: all fields filterable, sortable, and copyable where applicable. Set menu visibility (only Question, Screening, Session, Log appear in sidebar). Install `maatwebsite/laravel-nova-excel` and add `DownloadExcel` action to every resource.

**Creates:**
- `app/Nova/CategoryResource.php`
- `app/Nova/QuestionGroupResource.php`
- `app/Nova/QuestionResource.php`
- `app/Nova/ScreeningResource.php`
- `app/Nova/SessionResource.php`
- `app/Nova/AnswerResource.php`
- `app/Nova/LogResource.php`
- `app/Policies/CategoryPolicy.php`
- `app/Policies/QuestionGroupPolicy.php`
- `app/Policies/QuestionPolicy.php`
- `app/Policies/ScreeningPolicy.php`
- `app/Policies/SessionPolicy.php`
- `app/Policies/AnswerPolicy.php`
- `app/Policies/LogPolicy.php`
- Excel export action on all resources

**Validates:** Log in to Nova at `/cp`. QuestionResource, SessionResource, and LogResource appear in the sidebar. CategoryResource, QuestionGroupResource, and AnswerResource do not appear in the sidebar but are accessible via relational fields. Only Question.text is editable. Export to Excel works on each resource index. Attempting create/update/delete on read-only resources is denied.

**Browser Verification (Playwright MCP):**

*Verify sidebar and navigation:*
1. `browser_navigate` to `http://go-no-go.test/cp`
2. `browser_fill_form` -- log in with Nova credentials (email: jonathan@blijnder.nl, password: secret)
3. `browser_snapshot` -- confirm Nova dashboard loads, check sidebar items
4. `browser_take_screenshot` -- save as `step-12-nova-sidebar.png`
5. Verify sidebar contains: Questions, Screenings, Sessions, Logs
6. Verify sidebar does NOT contain: Categories, Question Groups, Answers

*Verify each resource index:*
7. `browser_click` "Questions" in the sidebar
8. `browser_snapshot` -- confirm question list loads with data
9. `browser_take_screenshot` -- save as `step-12-nova-questions-index.png`
10. `browser_click` "Sessions" in the sidebar
11. `browser_take_screenshot` -- save as `step-12-nova-sessions-index.png`
12. `browser_click` "Logs" in the sidebar
13. `browser_take_screenshot` -- save as `step-12-nova-logs-index.png`

*Verify read-only enforcement:*
14. `browser_click` "Sessions" in the sidebar
15. `browser_click` on a session row to view its detail
16. `browser_snapshot` -- confirm detail view loads but no Edit or Delete buttons are present
17. `browser_take_screenshot` -- save as `step-12-nova-session-detail-readonly.png`

*Verify Question.text is editable:*
18. `browser_click` "Questions" in the sidebar
19. `browser_click` on a question row to view its detail
20. `browser_snapshot` -- confirm an Edit button IS present for Questions
21. `browser_click` the Edit button
22. `browser_snapshot` -- confirm only the `text` field is editable; other fields are read-only or hidden
23. `browser_take_screenshot` -- save as `step-12-nova-question-edit.png`

*Verify Excel export:*
24. `browser_click` "Questions" in the sidebar
25. Select all items (checkbox), look for the Excel export action in the action dropdown
26. `browser_snapshot` -- confirm "Download Excel" action is available
27. `browser_take_screenshot` -- save as `step-12-nova-excel-export.png`

28. `browser_console_messages` with level `error` -- verify no errors

---

## Step 13: Testing

[ ] **Write automated tests for critical paths.**

Write PHPUnit feature tests for: pre-screening scoring (pass/fail at 5-point threshold), category questionnaire scoring (known inputs produce expected scores), session lifecycle (create, answer, complete, abandon), answer saving and validation, policy authorization (CRUD permissions per resource), Socialite auth flow (mock Azure AD callback). Write Cypress E2E tests for: full two-stage flow (landing → screening → category selection → questionnaire → result → again), pre-screening fail path (score <5 → No Go), scoring display updates during questionnaire, result page shows correct outcome.

**Creates:**
- `tests/Feature/ScreeningScoringTest.php`
- `tests/Feature/ScoringTest.php`
- `tests/Feature/SessionLifecycleTest.php`
- `tests/Feature/AnswerTest.php`
- `tests/Feature/PolicyTest.php`
- `tests/Feature/AuthTest.php`
- `tests/cypress/e2e/questionnaire-flow.cy.js`
- `tests/cypress/e2e/scoring-display.cy.js`
- `tests/cypress/e2e/result-page.cy.js`

**Validates:** `herd php artisan test` passes all PHPUnit tests. `npx cypress run` passes all E2E tests. No regressions in existing functionality.

**Browser Verification (Playwright MCP):**
No detailed browser verification needed for this step (automated tests handle validation). Perform a quick smoke test:
1. `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. Complete a full end-to-end flow: landing -> pre-screening (pass) -> select category -> basic info -> answer questions -> submit -> result page -> click Again
3. `browser_take_screenshot` at the result page -- save as `step-13-smoke-test-result.png`
4. `browser_console_messages` with level `error` -- confirm no regressions introduced by test setup

---

## Step 14: Production Readiness

[ ] **Final hardening, configuration, and review.**

Update `.env.example` with all required environment variables (Azure AD credentials, database, app URL, Nova license key). Create custom error pages (404, 500) using the app design system. Review eager loading across all controllers to prevent N+1 queries. Verify CSRF protection on all POST/PUT routes. Confirm `EncryptHistory` middleware is active on session routes. Run a full smoke test of the complete flow: SSO login, landing page, pre-screening (pass and fail paths), category selection, basic info, complete all questions, view result, click Again, check Nova admin, verify logs, export Excel.

**Creates:**
- Updated `.env.example`
- `resources/js/Pages/Error.vue` (or 404/500 specific pages)
- Query optimization passes on controllers
- Security checklist verification

**Validates:** Fresh clone + `composer install` + `npm install` + `herd php artisan migrate:fresh --seed` + `npm run build` produces a working application. Full user flow works end to end. Nova admin flow works. No console errors, no N+1 queries, no missing environment variables.

**Browser Verification (Playwright MCP):**
Full end-to-end smoke test covering every major screen:

*Frontend flow:*
1. `browser_navigate` to `http://go-no-go.test/login-jonathan`
2. `browser_snapshot` -- confirm landing page
3. `browser_take_screenshot` -- save as `step-14-e2e-landing.png`
4. `browser_click` the Continue button
5. `browser_fill_form` -- answer pre-screening questions (5+ Yes for pass path)
6. `browser_click` Submit
7. `browser_snapshot` -- confirm pass result with category picker
8. `browser_take_screenshot` -- save as `step-14-e2e-screening-result.png`
9. `browser_click` a category Start button
10. `browser_fill_form` -- fill all 4 basic info fields
11. `browser_click` Save
12. `browser_take_screenshot` -- save as `step-14-e2e-basic-info.png`
13. Navigate through all question groups, answering questions in each:
    - `browser_fill_form` for each group's questions
    - `browser_click` Next
    - `browser_take_screenshot` at representative groups -- save as `step-14-e2e-questions-{N}.png`
14. Verify `ScoreIndicator` is visible and color-coded
15. `browser_click` Submit/Complete on the final step
16. `browser_snapshot` -- confirm result page with GO/NO GO/Consult verdict
17. `browser_take_screenshot` -- save as `step-14-e2e-result.png`
18. `browser_click` the "Again" button
19. `browser_snapshot` -- confirm return to landing page

*Nova admin flow:*
20. `browser_navigate` to `http://go-no-go.test/cp`
21. `browser_fill_form` -- log in (email: jonathan@blijnder.nl, password: secret)
22. `browser_snapshot` -- confirm Nova dashboard
23. `browser_take_screenshot` -- save as `step-14-e2e-nova-dashboard.png`
24. `browser_click` "Sessions" in sidebar -- verify the just-completed session appears
25. `browser_take_screenshot` -- save as `step-14-e2e-nova-sessions.png`
26. `browser_click` "Logs" in sidebar -- verify log entries for the full flow
27. `browser_take_screenshot` -- save as `step-14-e2e-nova-logs.png`

*Error pages:*
28. `browser_navigate` to `http://go-no-go.test/nonexistent-page`
29. `browser_snapshot` -- confirm custom 404 page renders with app design system
30. `browser_take_screenshot` -- save as `step-14-e2e-404.png`

*Final check:*
31. `browser_console_messages` with level `error` -- verify zero console errors across the entire flow
32. `browser_network_requests` -- spot-check for any failed requests (non-2xx/3xx status codes)
