# Snapshot and Role-Specific Page Separation — Task Document

Source: "HeavyHaul Agent Snapshot and Role-Specific Page Separation" (short article, received 2026-09-13).
Related: `PILOT-MANAGEMENT-TASKS-2026-09-12.md`, `PILOT-PRO-TOOLS-INVOICING-TASKS-2026-09-12.md`.

## Current state (verified in codebase, 2026-09-13)

| Page group (article §3) | Today | Separated? |
|---|---|---|
| Broker Dashboard | `src/app/dashboard/broker-dashboard.tsx`, rendered by the single `/dashboard` route | Own component ✔ · own route ✘ |
| Broker Trip View | **Shared** `src/app/trips/[id]/trip-workspace.tsx` (one `TripWorkspace` for broker and carrier dispatch; role decides a few controls, e.g. status actions, permit-handling change, the "My Pilot" tab's broker/carrier variant) | ✘ same component, same route, same data loader |
| Carrier Dispatch Dashboard | `src/app/dashboard/carrier-dashboard.tsx`, same `/dashboard` route. **Imports `ViewToggle` from `broker-dashboard.tsx`** (cross-import between role pages) | Own component ✔ · own route ✘ · cross-import ✘ |
| Carrier Dispatch Trip View | the same shared `TripWorkspace` at `/trips/[id]` | ✘ |
| Pilot Company Dashboard | `src/app/dashboard/pilot-company-dashboard.tsx` (Phase 1 replica), `/dashboard?view=pilot-dispatch` | Own component ✔ · own route ✘ (query-string switch) |
| Pilot Company Trip / Assignment View | `src/app/trips/pilot-preview/[id]/pilot-trip-workspace.tsx` — **one component serves four viewers** (`?as=dispatch|driver|carrier|broker`) | Own route ✔ · own component ✘ |
| Carrier Driver App / Dashboard | `src/app/dashboard/driver-view.tsx`, `/dashboard` route; trip links carry `?view=driver` | Own component ✔ · own route ✘ |
| Carrier Driver Trip View | `/trips/[id]` renders `DriverView` when the person is a driver on the trip (`isDriverOnTrip`) | Own component ✔ · shared route ✘ |
| Pilot Driver App / Dashboard | `src/app/dashboard/pilot-driver-view.tsx`, `/dashboard?view=pilot-driver` | Own component ✔ · own route ✘ |
| Pilot Driver Assignment View | the same `pilot-trip-workspace.tsx` with `?as=driver` | ✘ |
| Admin / developer | `?view=` overrides, pilot testing bar, `RoleSwitcher` — all gated on `user.internal` | ✔ intentional testing path exists |
| Backend role context | Session role `broker / dispatcher / driver / admin`; trip role from `trip_participants`; `dashboardRole()` and `isDriverOnTrip()` in `src/lib/role-view.ts`; `/trips/[id]/page.tsx` loads the **same data** for broker and dispatcher. No pilot account roles yet (Phase 2 of the pilot work). | partial |
| Snapshot | 84 files changed and uncommitted since commit `2e1f8b0`; **no tags** in the repository | ✘ nothing preserved yet |

Shared components already in place and fine to keep shared (article §7 allows reuse): `ChatPanel`, `RiskTiles`, `StateNotesDialog`, `PilotAccessChooser`, `MyPilotTab`, `pilot-tools/*`, `ContactsPanel`, `StatusBadge`, `RoutePurchase`, `DimensionsEditor`, `AppShell`.

---

## Module A — Snapshot (article §1, §9)

### Task A1 — Preserve the current version as a timestamped reference snapshot
- Commit every current change (the 84 uncommitted files, which hold all pilot car, invoicing, driver and workspace work from 2026-09-12) and create an **annotated git tag** on that commit: `snapshot-2026-09-13-1630-ET` with the message "Approved reference version — 2026-09-13 4:30 PM ET — before role-specific page separation".
- Record the snapshot in the repo: a short `SNAPSHOTS.md` (or a section in this document) with date, time, tag name, commit hash, and purpose: "Current approved reference version before the next round of page-structure adjustments."
- Rollback rule: any later change that "creates confusion, breaks existing logic, or affects the wrong user interface" is compared against, or reverted to, this tag.
- **Nothing in Modules B–F starts before A1 is done.**

---

## Module B — Dashboard separation (article §2, §3, §7)

### Task B1 — One route per role dashboard
Today all five dashboards render from the single `/dashboard` route, selected by account role or `?view=`. Give each dashboard its own route so a change to one cannot reach another and so navigation stays inside the role (Module D). Route names are not specified in the article — see Open Question 1. Keep `/dashboard` as a redirect to the signed-in user's own dashboard.

### Task B2 — No cross-imports between role dashboards
`carrier-dashboard.tsx` imports `ViewToggle` from `broker-dashboard.tsx`. Move `ViewToggle` (and anything else one role page imports from another role page) into `src/components/app/` as a shared component. Rule going forward: a role page may import shared components; it may never import from another role's page file.

### Task B3 — Pilot Company Dashboard stays its own page group
It "may look similar to the carrier dispatch dashboard, but must still be treated as its own page group". It already has its own component; after B1 it gets its own route. Do not merge it with the carrier dashboard, and do not make the carrier dashboard render pilot content.

### Task B4 — Pilot Driver Dashboard stays its own page group
Same rule for `pilot-driver-view.tsx` versus `driver-view.tsx`: separate component (already), separate route (after B1). Reusing `ChatPanel` and other shared components is allowed; sharing the page is not.

---

## Module C — Trip view separation (article §2, §3, §4)

### Task C1 — Split the shared trip workspace into a Broker Trip View and a Carrier Dispatch Trip View
`TripWorkspace` is one component for both. Create two page-level components (for example `broker-trip-view.tsx` and `carrier-trip-view.tsx`), each owning its layout, tab set, actions and role rules, built from shared pieces (trip sidebar, chat, documents tab, people/invite form, history tab, state-info panel, "My Pilot" tab). Behaviour must be identical to today on day one; this task changes structure, not features. The article's example: "If we request a change on the Broker Trip View, it should not affect the Carrier Dispatch Trip View."

### Task C2 — Separate routes for the two trip views
`/trips/[id]` currently serves broker, carrier dispatch and carrier driver. After C1, route each role to its own trip view (names per Open Question 1). The carrier driver trip view (`DriverView`) already is its own component; give it its own route too instead of the `isDriverOnTrip` branch on the shared route.

### Task C3 — Separate Pilot Company Trip View from Pilot Driver Assignment View
`pilot-trip-workspace.tsx` serves the pilot company (`as=dispatch`), the pilot driver (`as=driver`) and the carrier/broker "My Pilot" previews (`as=carrier|broker`). Split into: a Pilot Company Trip/Assignment View, a Pilot Driver Assignment View, and keep the carrier/broker "My Pilot" preview as a design-review page under the admin testing tools. Shared pieces (scope card, permit list, `PilotToolsPanel`, `MyPilotTab`) stay shared.

### Task C4 — Per-role data loading for trip views
`/trips/[id]/page.tsx` loads one payload for everyone. Once views are split, each role's page loads only what that view uses (article §6: visible data based on role and page context). No change to what each role can see today — the payloads simply stop being shared.

---

## Module D — Internal navigation rule (article §5)

### Task D1 — Links stay inside the role's page environment
- Broker dashboard → broker trip view only.
- Carrier dispatch dashboard → carrier dispatch trip view only.
- Pilot company dashboard → pilot company trip/assignment view only (already links to the pilot preview workspace).
- Carrier driver app → carrier driver trip view only (today via `?view=driver`; after C2 via its own route).
- Pilot driver app → pilot driver assignment view only.
- Audit every `href` in the five dashboards and the five views (trip cards, warning rows, "Open" links, back links, the trip sidebar switcher, `BackLink`) and point them at the role's own route.

### Task D2 — Entry points that resolve a role
- `/invite/[token]` accept and `/trips/[id]` deep links (emails, copied invite links, the intake page) must land the person on **their** role's view of that trip, never on another role's page.
- `/dashboard` and `/history` links in the app shell resolve to the role's own dashboard.

### Task D3 — Cross-role jumping only for admin/developer testing
Keep the pilot testing bar, `?view=` overrides and the Pilot Car preview links, all gated on `user.internal`, as the one intentional way to open another role's pages ("unless it is an intentional admin/developer testing action"). Customers never get a route into another role's environment.

---

## Module E — Backend role / page context (article §6)

### Task E1 — Resolve a page context server-side
The backend must recognise the user and return the correct dashboard/trip context, one of: broker, carrier dispatch, pilot company, carrier driver, pilot driver, admin/developer. Today `role-view.ts` resolves `broker | dispatcher | driver` plus the internal-only pilot previews, and the trip page resolves driver-vs-workspace from the participant role. Extend this into one server-side resolver used by every role route: account role + trip participant role (+ admin override) → page context. Pilot company / pilot driver contexts depend on the pilot account roles from the pilot task document (Task A1 there); until those exist they resolve only through the admin preview.

### Task E2 — Permissions, data, actions, document access and navigation follow the page context
Each role route reads the resolved context and applies that role's rules for: what data is loaded (C4), which actions render, which documents are signed/returned, and where links go (D1). No new permission rules are introduced by this article — it requires that the existing ones are applied per page context rather than inside one shared page.

---

## Module F — Front-end structure and working rule (article §7, §8)

### Task F1 — Role-specific folders
Organise so role pages cannot be overwritten by accident: one folder per role for pages, routing and layouts (where a layout differs), with shared UI in `src/components/app/`. Shared components may exist; dashboards are not "one universal page". Exact folder/route names: Open Question 1.

### Task F2 — Task-wording rule (documentation)
Add to the project instructions (`AGENTS.md` or a `docs/` note) the article's rule: "Change Broker Dashboard" means broker dashboard only; the same for Broker Trip View, Carrier Dispatch Dashboard, Pilot Company Dashboard, Carrier Driver App, Pilot Driver App. No change is global unless the task says so. Also record the names of the ten page groups so tasks and code use the same vocabulary.

---

## Explicitly not in scope
- Any visual redesign of the pages — the article says the current result is good and must be protected.
- New features or permission rules — this is structure and routing only.
- Building the real pilot account roles (Phase 2 of the pilot work); the pilot pages here remain the Phase 1 replicas, just separated.

---

## Client answers (2026-09-13)

1. **Route scheme — role abbreviation, no nested subfolders, one link.** Abbreviations: **BD** broker dashboard, **CD** carrier dispatch, **PD** pilot dispatch, **CT** carrier truck driver, **PC** pilot car driver. Pattern: `<abbr>-dashboard` for the dashboard, `<abbr>-trip-workspace` + the **trip number** for the trip view, "all in one link". `trips.ref_code` is unique in the schema, so the trip number can be the URL key.
   Technical note (Next.js): a dynamic value cannot sit inside one path segment (`/cd-trip-workspace-HH-2041` is not a routable folder name), so the closest routable forms are (a) `/cd-dashboard` and `/cd-trip-workspace/HH-2041` — two segments, still one flat link per role, no role sub-tree; or (b) a single top-level dynamic route that parses `/cd-trip-workspace-HH-2041` itself. **(a) is proposed** (each role keeps its own folders; nothing is shared at the route level). Confirm — see Open Question 1.
2. **Snapshot — done.** Commit `5db8fc6`, tag `snapshot-2026-09-13-1630-ET`, pushed to `origin` (branch and tag). `SNAPSHOTS.md` records it. Personal meeting transcripts, order-page PDFs and test screenshots in the repo root were left untracked on purpose (`FEEDBACK-TRANSCRIPTS/` is already git-ignored for the same reason).
3. **Pilot company dashboard = its own page**, separate from the carrier dispatch dashboard: different tools and abilities, kept apart. No shared layout extraction between the two; only genuinely generic UI (buttons, pills, cards) may be shared.

## Resulting route map (to confirm)

| Page group | Dashboard | Trip / assignment view |
|---|---|---|
| Freight broker (FB) | `/fb-dashboard` | `/fb-trip-workspace/<trip number>` |
| Carrier dispatch (CD) | `/cd-dashboard` | `/cd-trip-workspace/<trip number>` |
| Pilot dispatch (PD) | `/pd-dashboard` | `/pd-trip-workspace/<trip number>` |
| Carrier truck driver (CT) | `/ct-dashboard` | `/ct-trip-workspace/<trip number>` |
| Pilot car driver (PC) | `/pc-dashboard` | `/pc-trip-workspace/<trip number>` |

`/dashboard` and `/trips/<id>` stay as redirects to the signed-in role's page so existing invite links and emails keep working.

## Implementation record (2026-09-13) — option (a) chosen and built

- **Module A** — done: commit `5db8fc6`, tag `snapshot-2026-09-13-1630-ET`, `SNAPSHOTS.md`.
- **Module B** — done: five dashboard routes (`/fb-dashboard`, `/cd-dashboard`, `/pd-dashboard`, `/ct-dashboard`, `/pc-dashboard`), each role's dashboard component moved into its own route folder; `ViewToggle` moved to `src/components/app/view-toggle.tsx` (no cross-imports between role pages); `/dashboard` is a redirect into the signed-in role's dashboard (internal `?view=` override kept).
- **Module C** — done: `/fb-trip-workspace/[ref]` (BrokerTripView) and `/cd-trip-workspace/[ref]` (CarrierTripView) are separate pages, routes and loaders over the shared trip-workspace shell (`src/components/app/trip-workspace/`), which takes a `pageContext` and a role-specific `tripHref`; `/ct-trip-workspace/[ref]` for the carrier driver with its own smaller loader (`loadDriverTripPayload`); `/pd-trip-workspace/[ref]` and `/pc-trip-workspace/[ref]` for the pilot company and pilot driver over the shared pilot-assignment shell; the carrier/broker "My Pilot" design review moved to `/admin/pilot-preview/[ref]?as=carrier|broker` (internal only). Loaders: `src/lib/data/dashboard.ts`, `src/lib/data/trip-page.ts`.
- **Module D** — done: every dashboard and view links to its own role route by trip number; `/trips/<id>` resolves the person's role on that trip and redirects, so invites, emails, history, alerts and the trip card keep working and always land inside the person's own environment; `?view=driver` links removed from the driver app; pilot testing bar, banner, user menu and signup hand-off point at the new routes.
- **Module E** — done: `src/lib/page-context.ts` (`contextForAccount`, `contextForTrip`, `mayOpenContext`, `dashboardPath`, `tripPath`) and `src/lib/role-route.ts` (`requireRoleRoute`): a customer may open only their own role's routes, internal admins any; pilot routes are internal-only until pilot account roles exist. Tests: `tests/page-context.test.ts`.
- **Module F** — done: role folders as above; the task-wording rule and page-group vocabulary added to `AGENTS.md`.
- Behaviour on day one is unchanged by design: same components, same data, same rules — only the route, folder and loader per role are separate.

## Open questions

None outstanding. (Route form: option (a) chosen. 2026-09-13 later: broker abbreviation changed from BD to **FB** — "they really like to be called freight brokers" — routes `/fb-dashboard`, `/fb-trip-workspace/<trip number>`.)

## Mobile / tablet QA — Freight Broker pages (2026-09-13)

Nash: "Test all freight broker pages for the mobile view… cell phone and also on iPad… no mobile view errors and issues and misalignments."

Tested at 375×812 (phone) and 768×1024 (iPad) through a development-only preview route (`/dev-preview/fb/dashboard`, `/dev-preview/fb/trip` — renders the FB pages on demo data, 404 outside `next dev`) because the real pages need a signed-in session.

| Where | Found | Fix |
|---|---|---|
| App header (phone) | "History" slid under the account avatar | Nav is its own scrollable lane; the avatar block never shrinks (`app-shell.tsx`, shared by every role) |
| FB dashboard filter row (phone) | "Waiting on Permits" chip clipped behind the Blocks/Table toggle | Chips wrap; the toggle keeps its own space (`fb-dashboard/broker-dashboard.tsx`) |
| FB trip view · Overview permit cards (phone) | State name squeezed to "Oh…" by the "Use 1 Express Route credit" chip | Card header wraps; title takes the width, chip drops under it (shared trip-workspace shell — the carrier view gets the same card) |
| FB dashboard (iPad) | none — two-column cards, tiles, warnings and filters all fit | — |
| FB trip view · chat / Trip Info / Docs / People / History (phone + iPad) | none beyond the two above; chat suggestion chips scroll sideways by design | — |

Two of the three fixes touch shared UI (the header and the permit card). They are layout-only and change nothing role-specific, so they apply to every role's pages.

## Mobile / tablet QA — Carrier Dispatch and Pilot Dispatch pages (2026-09-13)

Nash: "check the carrier dispatch pages and the pilot dispatch pages on cell phones and iPads… 100% mobile friendly." Same method: the dev-only preview route now covers `/dev-preview/cd/…` and `/dev-preview/pd/…` (dashboard, trip).

| Where | Found | Fix |
|---|---|---|
| CD dashboard tab strip (phone + iPad) | Seven tabs overflowed and scrolled sideways; "Drivers / Reports" hidden | Tabs wrap onto extra lines (`cd-dashboard/carrier-dashboard.tsx`) |
| CD dashboard trip list (phone) | Opened on the nine-column table, which only scrolls sideways at 375px | With no saved choice, a phone opens on Blocks; the toggle and any saved choice still win |
| CD trip view (phone + iPad) | none — same shell as the freight broker view, already fixed (header, permit cards) | — |
| PD dashboard assignments (phone) | Nine-column table cramped and sideways-scrolling | One card per assignment below `sm` (lane, carrier, shared scope, permits, driver, unit, status, Open trip / Details); table from `sm` up (`pd-dashboard/pilot-company-dashboard.tsx`) |
| PD dashboard manage menu, tiles, setup card (phone + iPad) | none — menu wraps, tiles reflow to 3 columns | — |
| PD trip workspace (phone + iPad) | Chat and trip-info panels sat side by side with no phone layout | Below `lg` the workspace shows chat OR trip info with a "Trip Info" / "← Back to chat" toggle, like the real workspace; the assignment switcher stays hidden below `lg` (`pilot-assignment/pilot-assignment-workspace.tsx`) |
| PD documents gate + Admin bypass (phone) | none | — |

Not changed: the admin-only preview banner is tall on a phone because of its link pills; it is internal tooling and never shown to customers.

## Trip views · route formats shown by default (2026-09-13)

Nash: "when the route is already purchased and the route is ready… right now it requires me to click in order to see the GPX, Hummer and part one two three… for the trip view on broker, carrier, and pilot, we should have them displayed by default when loading if the route is ready… if I wanna hide, I can click on the [chip] and it will hide them."

- Freight broker and carrier dispatch trip views (shared trip-workspace shell, permit cards): the delivered formats now render open on load; the "✓ Route ready" chip toggles them (`showParts` defaults to `true`).
- Pilot dispatch and pilot driver assignment views (pilot-assignment shell): a ready route shows its formats (Google Maps parts, GPX, Hummer GPS — demo until the backend) open on load; the "✓ Route ready" chip toggles them.
- Not changed: the carrier driver's Active Trip route rows were not part of this feedback.

## Freight Broker Trip View · phone landing (2026-09-13)

Nash: "by default, I would like to open trip info… a floating microphone icon on the bottom right that when he clicks, it opens the chat section… only on the mobile version… First, I wanna see the trip information, and then I can start asking questions."

- Freight Broker Trip View only: below desktop width the page now lands on **Trip Info**; a floating amber microphone button (bottom right, phone/tablet only) opens the Agent chat; the existing "Trip Info" button in the chat header returns. Deep links with `?tab=chat` still open the chat.
- Implemented as a `mobileLanding` option on the shared trip-workspace shell, set only from `fb-trip-workspace/[ref]/broker-trip-view.tsx`. The carrier dispatch trip view is unchanged (still lands on chat).

Follow-up the same day: the info header's button now reads **🎙 Ask the Agent** (was "← Back to chat"); the chat header's **📋 Trip Info** button is bigger and navy so it is easy to find; and on phones a **left/right swipe** switches between chat and trip info (swipe left → chat, right → trip info; drags inside sideways-scrolling strips and form fields are ignored). Buttons, swipe and the floating microphone are the three ways to move between the two. Still freight-broker only (tied to the `mobileLanding="info"` option); the carrier dispatch view keeps its original buttons.

## Regression fixed · Freight Broker "New Trip Request" (2026-09-13)

Nash: "It looks like we lost the functionality that we had for the new trip request on the broker side… this is where he invites carrier dispatches… requesting carrier dispatches to upload the permits… decides if the carrier dispatcher has the right to upload or he must buy from Synchron Permits."

Cause: `/trips/new` chose its flow from the raw account role (`user.role === 'broker'`). The admin account (Nash's, since 2026-09-11) is not `broker`, so it fell through to the generic "Create a trip" form; the broker request form, the intake API and the permit-handling choice were never removed. Fix: the page now branches on the **page context** (`contextForAccount` — admin and broker → freight broker, dispatcher → carrier), and the broker intake page self-heals for that context (`loadBrokerPage`), so "New Trip Request" opens the request flow again: upload the rate confirmation, invite the carrier dispatcher, choose Carrier Will Upload Permits or Synchron Permits (with who pays), then the workspace starts as Waiting on Permits. The freight broker dashboard's intake-link card comes back for the admin account for the same reason.

Follow-up (2026-09-13): the freight broker dashboard's intake buttons depend on the broker's intake-page record, which the admin account only gets since the fix above; both are back. A "View My Intake Page" header button was added and then **removed the same day** at Nash's request to match the approved design: the header keeps **Copy Intake Link · + New Trip Request · + New Trip**, and the intake card keeps **View Public Page →**.

## Freight Broker · `/fb-new-trip-request` (2026-09-13)

Nash: "a specific link when the broker clicks on new trip request… `fb-new-trip-request`… the broker will drag and drop the rate confirmation. He can choose any of the dispatchers from his contact list, or he can just type the dispatch name and the dispatch email and dispatch phone number and send request… two options: allow carrier to upload his own permits, or request carrier to purchase permits from trusted partner Synchron Permits."

- New route `/fb-new-trip-request` (freight broker page group); the dashboard's **+ New Trip Request** opens it; `/trips/new` forwards a freight broker there. The request form moved into the route folder.
- Rate-confirmation drag & drop, the dispatch fields and the two permit options (with who pays Synchron) were already in the form. Added **choose a dispatcher from your contacts**: a picker above the dispatch fields lists the broker's saved dispatchers and fills name / email / phone; typing still works. After a request is sent, the dispatcher is saved to the broker's contacts (role `dispatcher`) so the list builds itself — **migration `0016_dispatcher_contacts.sql`** widens the saved-contact role check; apply it with the other migrations.

## Regression fixed · intake buttons and New Trip Request fields missing (2026-09-13)

Cause: the pilot testing bar's **Broker dashboard** button is a plain link (no broker test login exists since 2026-09-11), so after switching to the carrier or driver test account the session stays that account. The freight broker routes still open for an internal admin, but the broker intake page lookup was gated on the session role, returning nothing — hence no **Copy Intake Link / View My Intake Page** and, on `/fb-new-trip-request`, the fallback text instead of the form. Fix: internal sessions always get (or self-heal) an intake page on the freight broker routes. Full fidelity still comes from adding a broker entry to `AUTH_USERS` so the Broker button becomes a real session switch.

Also: the New Trip Request dispatcher picker now lists the dispatchers already on the broker's trips (de-duplicated by email) in addition to saved contacts, so it is useful before migration 0016 and before any request has been sent (`loadBrokerDispatchers`).

## Role-specific New Trip routes (2026-09-13)

Nash: "the design of the new trip page depends on the user type… FB dash new trip… CD dash new trip… PD dash new trip. Depending from which dashboard you click on the button, the right link should open."

- `/fb-new-trip` — the freight broker's own trip (simple form; `fb-new-trip/new-trip-form.tsx`). The dashboard's **+ New Trip** opens it; **+ New Trip Request** still opens `/fb-new-trip-request`.
- `/cd-new-trip` — the carrier wizard (`cd-new-trip/carrier-wizard.tsx`: drag in the rate con, the trip fills itself, invite your people).
- `/pd-new-trip` — pilot dispatch **private trip** (article §40; Phase 1 replica): origin, destination, carrier (optional), commodity, dates, notes, permit copies drop zone. Saved in the per-browser preview store and listed on the pilot dispatch dashboard; opens in the pilot workspace.
- `/trips/new` is now a redirect by page context so old links keep working.

Follow-up (2026-09-13): the same phone logic now applies to the **Carrier Dispatch Trip View** (`mobileLanding="info"` set from its page) and to the **Pilot Dispatch / Pilot Driver assignment workspace** (built into the pilot shell): land on Trip Info, 🎙 Ask the Agent on the info header, 📋 Trip Info on the chat header, swipe left/right, floating microphone.
