18 KiB
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
[ ] 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 resolvertailwind.config.js-- custom color tokensresources/views/app.blade.php-- Inertia root template with@viteand@inertiaHeadresources/js/app.js-- Vue + Inertia app bootstrapapp/Http/Middleware/HandleInertiaRequests.phpcomposer.jsonupdates:inertiajs/inertia-laravel,laravel/socialitepackage.jsonupdates: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.
Step 2: Database -- Migrations and Models
[ ] 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.phpdatabase/migrations/*_create_question_groups_table.phpdatabase/migrations/*_create_questions_table.phpdatabase/migrations/*_create_screenings_table.phpdatabase/migrations/*_create_screening_answers_table.phpdatabase/migrations/*_create_sessions_table.phpdatabase/migrations/*_create_answers_table.phpdatabase/migrations/*_create_logs_table.phpapp/Models/Category.phpapp/Models/QuestionGroup.phpapp/Models/Question.phpapp/Models/Screening.phpapp/Models/ScreeningAnswer.phpapp/Models/Session.phpapp/Models/Answer.phpapp/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.
Step 3: Authentication
[ ] 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.phpconfig/services.phpAzure AD configuration- Route registrations in
routes/web.phpfor/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.
Step 4: Frontend Layout and Shared Components
[ ] 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.vueresources/js/Components/AppLogo.vueresources/js/Components/PageHeader.vueresources/js/Components/AppButton.vueresources/js/Components/ScoreIndicator.vue- Shared data configuration in
HandleInertiaRequests.php EncryptHistorymiddleware 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.
Step 5: Page Stubs and Click-Through Flow
[ ] 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.vueresources/js/Pages/Screening/Show.vueresources/js/Pages/Screening/Result.vueresources/js/Pages/Session/Show.vueresources/js/Pages/Session/Result.vueapp/Http/Controllers/LandingController.phpapp/Http/Controllers/ScreeningController.phpapp/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.
Step 6: Seeders -- Categories, Question Groups, and Questions
[ ] 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.phpdatabase/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.
Step 7: Landing Page and Pre-Screening Flow
[ ] 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.
Step 8: Basic Info Form
[ ] 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 inupdatemethod - 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.
Step 9: Questionnaire Flow -- Question Rendering and Answer Saving
[ ] 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 question groups as steps within Session/Show. Each step shows its questions, scoring instructions (if present), and a "Next" / "Previous" navigation. 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 as the final step.
Creates:
resources/js/Components/QuestionCard.vue- Updated
resources/js/Pages/Session/Show.vue-- step navigation, question group rendering - Updated
app/Http/Controllers/SessionController.php-- answer saving logic inupdate - Answer validation rules
Validates: Navigate through all question groups for a category. Each question renders the correct UI pattern (radio buttons, text-only, details visibility). Answers save without page reload. Navigating away and back retains saved answers. All 6 question patterns render correctly.
Step 10: Scoring and Result
[ ] 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 inupdate(completion) andresult - 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.
Step 11: Activity Logging
[ ] 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.
Step 12: Nova Resources and Policies
[ ] 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.phpapp/Nova/QuestionGroupResource.phpapp/Nova/QuestionResource.phpapp/Nova/ScreeningResource.phpapp/Nova/SessionResource.phpapp/Nova/AnswerResource.phpapp/Nova/LogResource.phpapp/Policies/CategoryPolicy.phpapp/Policies/QuestionGroupPolicy.phpapp/Policies/QuestionPolicy.phpapp/Policies/ScreeningPolicy.phpapp/Policies/SessionPolicy.phpapp/Policies/AnswerPolicy.phpapp/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.
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.phptests/Feature/ScoringTest.phptests/Feature/SessionLifecycleTest.phptests/Feature/AnswerTest.phptests/Feature/PolicyTest.phptests/Feature/AuthTest.phptests/cypress/e2e/questionnaire-flow.cy.jstests/cypress/e2e/scoring-display.cy.jstests/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.
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.