Documents the parallel 4-agent QA run (~145 test cases), the 18 bugs found, and all fixes applied in the preceding 4 commits. Adds session 5 changelog and QA coverage summary table.
21 KiB
Proposal System — Retrospective (2026-05-19, updated 2026-05-20, session 5 added 2026-05-20)
Executive Summary
Across four sessions (2026-05-18, 2026-05-19, and two on 2026-05-20), the project progressed from a broken mobile CI pipeline to a functional mobile app on device AND a fully tested web frontend with working dev-mode authentication, proposal lifecycle, and admin workflows.
Sessions 1-3 (Mobile): The mobile app went from a broken CI pipeline to a functional app running on a physical device with working email/password authentication. Three distinct launch crashes were resolved, Cognito SRP login was validated end-to-end, and multiple UI issues were fixed. The app boots, authenticates, and renders on iOS 26 hardware. However, it cannot communicate with the backend API from a device, Google OAuth crashes the app, and the branch has not been merged to main.
Session 4 (Web): Full local web testing exposed six bugs in the API and frontend: enum serialization failures, identity/role confusion in dev-login, incorrect proposal ownership filtering, Autocomplete binding issues, audit log format errors on Postgres jsonb columns, and missing API idempotency. All were fixed in four logical commits. The web app's core workflow — submit as dispatcher, review/edit/approve/send as admin — is now functional end-to-end in dev mode.
Session 5 (Automated QA): Ran 4 parallel test agents covering ~145 test cases across every API endpoint and every frontend page. Found 18 bugs (2 critical, 4 high, 7 medium, 5 low). All 16 actionable bugs fixed in 4 commits. Critical: admin dashboard LINQ crash (EF Core can't translate TimeSpan.TotalHours to SQL) and revision endpoint 500 (unique constraint on ProposalNumber). High: dispatcher dashboard data exposure (missing mine filter), no frontend role guards on admin routes, submittedByName null on mutation responses, invalid role silently defaulting to Admin. Also extracted duplicated STATUS_COLORS and format utilities into shared modules, wired the admin dashboard filter dropdowns, and added debounce to customer search.
Systemic findings: The web session revealed two architectural gaps: (1) audit logging was fragile — a format error in a non-critical audit write could roll back an otherwise successful save, and (2) state machine transitions lacked idempotency, meaning retries or UI double-clicks could produce 500 errors instead of graceful no-ops. Both are patterns that would have surfaced in production under real load. Session 5 added a third: the frontend had no authorization enforcement — ProtectedRoute checked authentication but not role, so any logged-in user could navigate to admin pages by URL.
Standards Compliance Status
| Rule | Status | Detail |
|---|---|---|
| Naming conventions | PASS | kebab-case throughout, branch name follows pattern |
| CI/CD pipeline exists | PASS | deploy-mobile.yaml and deploy.yaml both trigger on push to main |
| OIDC deploy role | PASS | githubdeploy-proposal-system |
| README accurate | PARTIAL | Root README updated (0.85, deploy status). mobile/README.md incomplete (no auth/device docs). Web dev mode not documented. |
| Confluence updated | FAIL | No Atlassian MCP. Architecture Map missing mobile pipeline, Cognito auth flow, and web dev-mode setup |
| Memory updated | UPDATED | Project memory updated with session 4 web fixes. New feedback memories created for API patterns. |
| Git workflow | PASS | All sessions used feature branch mobile/fix-react-version-and-ui |
| Commit messages | PASS | Imperative mood, explains "why", logically grouped changes |
| CDK callback URL fix | PASS | Fixed in CDK + live Cognito via AWS CLI |
| Pre-push lint/typecheck | NOT VERIFIED | Did not run typecheck before pushing — should have per CLAUDE.md hook |
| Dev secrets excluded | PASS | appsettings.Development.json (dev signing key) kept untracked, not committed |
| API idempotency | FIXED | Approve, MarkSent, Revise transitions now idempotent. Audit failures isolated from saves. |
Required Memory Updates
Completed this session
-
project_proposal_system.md— Updated with session 4 web fixes: dev-mode setup, enum serialization, audit log format, idempotent transitions, scoped My Proposals filtering. -
feedback_mobile_deploy_lessons.md— All three session-3 lessons added (React pinning, import type, dev API URL). Done in session 3. -
feedback_react_version_pinning.md— Created in session 3. Done. -
feedback_api_idempotency.md— NEW: State machine transitions must be idempotent. Audit writes must not roll back successful saves. -
feedback_jsonb_audit_format.md— NEW: Postgres jsonb columns require valid JSON, not plain strings. Audit details must be wrapped.
Still outstanding
reference_mobile_testflight.md— The ⚠️ note about "username/password flow needs to be added" is now resolved but not yet updated in the file.
Required Documentation Updates
| Doc | Status | Action |
|---|---|---|
Root README.md |
Updated (session 2) | Needs update: add web dev-mode setup instructions (DevMode, dev-login, local Postgres) |
mobile/README.md |
Exists but incomplete | Add: email/password auth via Cognito SRP, patch-package for netinfo iOS 26 fix, React version pinning requirement, local device testing setup |
api/ dev setup |
MISSING | No documentation for local API development: appsettings.Development.json template (without secrets), Docker Compose for Postgres, dev-login endpoint usage |
| Confluence "AWS Architecture Map" | OUTSTANDING | Still blocked — no Atlassian MCP. Needs: mobile CI/CD pipeline, Cognito auth flow, web dev-mode architecture |
CDK foundation-stack.ts |
Updated (session 2) | Callback URLs fixed, CfnOutputs added for client IDs |
Reusable Skills / Automations
| Candidate | Type | ROI | Description |
|---|---|---|---|
| React version coherence check | CI step | CRITICAL | node -e script that reads node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js, extracts the hardcoded version string, and compares against node_modules/react/package.json. Fails if mismatch. Would have caught the exact crash from session 3. |
| API idempotency test suite | Integration test | HIGH | For each state-machine endpoint (approve, markSent, revise), call twice with same input and assert both return 200 with matching response. Would have caught all three idempotency bugs from session 4. Pattern: assert f(f(x)) == f(x) for all mutation endpoints. |
| Audit isolation pattern | Code pattern | HIGH | Wrap all non-critical audit writes in try/catch so they never roll back the primary operation. Consider a SafeAuditService decorator or middleware. Session 4's bulk update 500 error was caused by audit failure after a successful save. |
| iOS device smoke test script | Script / Runbook | HIGH | Checklist for post-build device testing: connect device, Metro --host <LAN_IP>, build with automatic signing, verify login, test auth flow. |
patch-package audit CI step |
CI check | MEDIUM | Verify patches in mobile/patches/ still apply cleanly and patched packages haven't been updated. |
| Dev-mode login test harness | Script | MEDIUM | Script that exercises all three dev-login roles (SysAdmin, Admin, Dispatcher) and verifies each returns a distinct user identity with correct role. Would have caught the role/identity confusion bugs immediately. |
| Cognito ID token user extraction | Utility | LOW | parseUserFromIdToken() in auth.ts — reusable for any Cognito-backed app. |
Key Lessons Learned
React Native Runtime (Sessions 1-3)
-
React version MUST be pinned exactly, not with semver range. RN 0.85.3's bundled
ReactNativeRenderer-dev.jshas a hard check:if ("19.2.3" !== isomorphicReactPackageVersion). The peer dependency says^19.2.3, npm resolves to 19.2.6, and the app crashes with an opaque "Cannot read property 'default' of undefined" ingetPaperRenderer. Pin"react": "19.2.3"in package.json. -
import typeis not reliably erased for modules with native initialization.import type { CognitoUserSession } from 'amazon-cognito-identity-js'was NOT stripped by Babel in RN's build pipeline. The module eagerly initialized native crypto at import time, causing a crash. Fix: remove the import entirely and useany, or use dynamicawait import(). -
localhostin dev config is the phone, not the Mac.API_URL: 'http://localhost:5000/api'in dev mode is unreachable from a physical device. Need either LAN IP or a fallback strategy.
iOS 26 Specific (Sessions 1-3)
-
CoreTelephony APIs removed without replacement.
@react-native-community/netinfov12.0.1 still calls deprecated APIs. Requiredpatch-packagewithrespondsToSelector:guards. -
iPhone Mirroring is the fastest way to test on device. Built into macOS 26, gives full touch control. Developer Mode on the phone is under Settings > Privacy & Security.
.NET API / Web Frontend (Session 4)
-
Postgres jsonb columns reject plain strings. The
detailscolumn onAuditLogsis typedjsonb. Writing a bare string like"Added: Widget repair"produces Postgres error 22P02. Wrap in a JSON object:JsonSerializer.Serialize(new { message = details }). This is easy to miss because SQLite and SQL Servernvarcharaccept anything. -
System.Text.Json requires explicit
JsonStringEnumConverterfor enum round-tripping. Without it, sending"ServiceCategory": "Plumbing"from the frontend produces a validation error because the default deserializer expects an integer. Must addoptions.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())inAddJsonOptions. -
State machine transitions must be idempotent. Approve, MarkSent, and Revise all threw
InvalidOperationExceptionon repeat calls (e.g., from network retries or UI double-clicks). Fix: if already in the target state, return current entity instead of throwing. This is especially critical for mobile clients with unreliable connectivity. -
Audit writes must never roll back successful business operations.
BulkUpdateAsyncsaved line items successfully, then_audit.LogAsyncthrew (due to the jsonb format bug), and the entire request returned 500. The user saw "unexpected error" even though their data was saved. Fix: wrap non-critical audit calls in try/catch. -
Dev-login must produce deterministic, distinct identities per role. Using
Guid.NewGuid()for CognitoSub meant the same email produced different identities across logins. Using a single hardcoded email for all roles meant switching roles didn't actually switch users. Fix: deterministic sub (dev-{email}), distinct email/name per role, and update role on existing user if changed. -
"My Proposals" means ownership, not role-based filtering. Initial implementation filtered by role (show all for admins, filter for dispatchers). The correct behavior: "My Proposals" always shows only proposals the current user submitted, regardless of role. Admins see all proposals in the separate Admin Queue.
Process (All Sessions)
-
Feature branch for iterative debugging works. Session 2 pushed 10+ commits to main. Sessions 3-4 used
mobile/fix-react-version-and-ui— all fixes stay off main until ready. -
User testing catches what type systems and linters can't. Session 4's six bugs all passed TypeScript compilation and would pass unit tests. They were logic errors in business rules, serialization config, and identity management that only surfaced through manual workflow testing. Interactive testing with role-switching is essential before any deploy.
Highest ROI Improvements
Ranked by impact-to-effort:
-
Add API idempotency integration tests (1 hr) — For each state-machine endpoint, call twice with same input and assert both return 200. Pattern:
assert f(f(x)) == f(x). Would have caught 3 of session 4's bugs automatically. Generalizable to any future endpoint. -
Add React version coherence CI check (30 min) — A 10-line node script that extracts the expected version from the bundled renderer and compares to installed React. Prevents the most time-consuming crash from session 3.
-
Isolate audit writes from business operations (30 min) — Create a
SafeAuditServicewrapper or add try/catch to all audit calls in services. The pattern already exists inLineItemServicebut should be systematic, not ad-hoc. A single audit format bug caused a 500 on an otherwise successful operation. -
Add
.gitignoreto API project (5 min) —appsettings.Development.jsoncontains dev signing keys and must not be committed. Currently relying on manual exclusion. Add it to.gitignorewith a template file (.example) that documents the required keys without values. -
Document web dev-mode setup (15 min) — No docs exist for running the API locally: Docker Compose for Postgres,
appsettings.Development.jsontemplate, dev-login endpoint, role switching. This will block any new developer. -
Merge
mobile/fix-react-version-and-uiand deploy (5 min) — Branch has 8 commits of critical fixes (sessions 3-4). Current TestFlight build crashes. Must merge before next submission. -
Fix dev API_URL for physical devices (10 min) —
localhost:5000is unreachable from iPhone. Blocks all API-dependent mobile features during device testing. -
Pin all RN ecosystem versions exactly (5 min) — Already done for React; extend to all
@react-native/*packages.
Outstanding Risks or Follow-Ups
| Priority | Item | Risk | Branch/Location |
|---|---|---|---|
| BLOCKING | Feature branch not merged — TestFlight build still crashes | Any TestFlight tester or Apple reviewer will see a crash | mobile/fix-react-version-and-ui |
| BLOCKING | Google OAuth crashes the app on tap | Apple reviewer may try both login methods | auth.ts → react-native-app-auth → Cognito Hosted UI |
| HIGH | appsettings.Development.json not in .gitignore |
Dev signing key could be accidentally committed | api/src/ProposalSystem.Api/ |
| HIGH | Dev API_URL is localhost:5000 — all API calls fail on device |
Proposals can't be created, viewed, or searched on device | config.ts |
| HIGH | Placeholder app icons (solid blue squares) | Unprofessional for TestFlight / App Store | ios/ProposalSystem/Images.xcassets |
| HIGH | No web dev-mode setup documentation | New developer can't run the system locally | Root README / api/ docs |
| MEDIUM | Confluence Architecture Map still missing mobile pipeline | Documentation debt per CLAUDE.md | Page 1540098 |
| MEDIUM | react-native-paper has known issues with RN 0.85 |
May surface as bugs in production | GitHub issues #4889, #4905 |
| MEDIUM | netinfo patch needs monitoring for upstream fix | Patches can silently break on version bumps | patches/@react-native-community+netinfo+12.0.1.patch |
| MEDIUM | DeleteAsync in LineItemService doesn't update TotalBidAmount |
Deleting a line item leaves the proposal total stale | LineItemService.cs:113 |
| LOW | no-floating-promises ESLint rule still not added |
Class of crash from session 2 can recur | mobile/.eslintrc |
| LOW | Cognito test user password in memory file | Acceptable for internal test account | reference_mobile_testflight.md |
| LOW | 4 Dependabot vulnerabilities open | Adam deferred these | GitHub Security tab |
Session 5 Changelog — Automated QA & Bug Fixes (2026-05-20)
All fixes on mobile/fix-react-version-and-ui (4 commits, not yet on main):
d00c552 Fix admin dashboard LINQ crash, revise unique constraint, and mutation response data
AdminController.cs— Rewrote avgTurnaround query to fetch approved times to memory before computing TotalHours (EF Core/Npgsql cannot translate TimeSpan.TotalHours)ProposalService.csReviseAsync — Append-R{n}suffix to revision ProposalNumber to avoid unique index violationProposalService.csUpdate/Approve/MarkSent — Added.Include(p => p.SubmittedBy)so mutation responses return submittedByName
8666010 Validate dev-login input: reject empty email and invalid role
AuthController.cs— Return 400 for empty/whitespace email and invalid role strings; default role changed from Admin to Dispatcher (least privilege)
4d72b63 Add frontend role guards, fix dashboard data exposure, and harden UX
ProtectedRoute.tsx— NewRoleGuardcomponent for role-based route protectionApp.tsx— Wrapped admin routes withRoleGuard;/admin/*requires Admin/SysAdmin,/admin/usersrequires SysAdminDashboard.tsx— Addedmine: trueto dashboard query so dispatchers only see their own proposalsAdminWorkspace.tsx— Auto-save dirty changes before approving (was silently discarding edits)ProposalFormPage.tsx— Added onError toast handler; added 300ms debounce on customer autocomplete searchadmin.ts— Stopped swallowing errors in getPdf; fixed AuditEntry.details type tostring | nullLoginPage.tsx— Fixed pre-existing TS error with noUncheckedIndexedAccess
5e89e42 Extract shared constants and format utils, wire admin dashboard filters
constants/index.ts— Added sharedSTATUS_COLORSandPRIORITY_COLORS(removed from 5 files)lib/format.ts— New sharedformatCurrency,formatDate,formatDateTime(removed from 4 files)AdminDashboard.tsx— Wired Category and Priority filter dropdowns tousePaginatedListextraParamsProposalDetailPage.tsx— Added 'Revised' to STATUS_ORDER so stepper renders correctly
QA Coverage Summary
| Test Area | Tests | Pass | Fail | Agent |
|---|---|---|---|---|
| Auth & RBAC | 35 | 31 | 4 | Auth agent |
| Proposal CRUD & State Machine | 38 | 35 | 3 | Proposal agent |
| Line Items, Customers & Misc | 42 | 39 | 3 | Misc agent |
| Web Frontend Code Review + API | ~30 | ~25 | ~5 | Frontend agent |
| Total | ~145 | ~130 | ~15 | — |
Session 3 Changelog — Mobile Device Testing (2026-05-20)
Fixes on mobile/fix-react-version-and-ui (not yet on main):
- Pin React 19.2.3 — fixes renderer version mismatch crash
- Remove
import typefrom cognito-auth.ts — fixes eager module init crash - Auth fallback to ID token —
loginWithCredentialsparses user from JWT when backend API unreachable - Safe area fixes — Settings gets top+bottom edges; Dashboard/AdminDashboard use bottom-only (nav header handles top)
- Pull-to-refresh separation — filter chip taps no longer trigger refresh animation on proposal queue
- Welcome name — shows first name or email prefix instead of full email
- Service category chips — wrapping
Chipcomponents replace truncatedSegmentedButtons - ErrorBoundary — added to App root for crash visibility
Already on main (sessions 2-3):
- Email/password login screen — TextInput form + Cognito SRP via
amazon-cognito-identity-js - Cognito config populated — real values from AWS CLI
- CDK callback URL fix —
com.seahavenind.proposals://auth/callback - netinfo iOS 26 patch —
patch-packagewithrespondsToSelector:guards - Auto-deploy enabled —
deploy-mobile.yamltriggers onmobile/**push to main - Root README updated — RN 0.85, deploy status corrected
mobile/README.mdcreated — local dev, signing, CI/CD docs
Session 4 Changelog — Web Local Testing (2026-05-20)
All fixes on mobile/fix-react-version-and-ui (4 commits, not yet on main):
f44caba Fix JSON enum serialization and audit log jsonb format
Program.cs— AddedJsonStringEnumConvertertoAddJsonOptionsso frontend string enums deserialize correctlyAuditService.cs— Wrapped plain-string audit details inJsonSerializer.Serialize(new { message = details })for Postgres jsonb columnglobal.json— Relaxed SDK version from 8.0.400 to 8.0.100 to match installed .NET SDK
f37c2aa Fix dev-login role switching, distinct users, and user resolution races
AuthController.cs— Dev-login now updates role on existing user; CognitoSub is deterministic (dev-{email})CurrentUserService.cs— AddedDbUpdateExceptioncatch on concurrent user creation with retry lookupLoginPage.tsx— Distinct dev user per role (SysAdmin=Adam, Admin=Sarah, Dispatcher=Mike)
9b97b7b Fix proposal workflow: scoped My Proposals, idempotent state transitions
ProposalService.cs— New proposals created asInReview(notDraft); My Proposals filters byMineparameter (not role);ApproveAsync,MarkSentAsync,ReviseAsyncall idempotentLineItemService.cs— Audit writes wrapped in try/catch so failures don't roll back successful savesProposalDtos.cs— Addedbool Minefilter parameterproposals.ts/ProposalListPage.tsx— Frontend passesmine: truefor My Proposals page
2645d97 Fix customer name not binding from Autocomplete free text input
ProposalFormPage.tsx—onInputChangewithreason === 'input'now callshandleChange('customerName', value)alongside search