dual-dashboard — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited dual-dashboard (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
These are distinct interfaces — no shared dashboard with toggled views. Both sides must feel like the product was built specifically for them.
There are exactly two authenticated dashboard experiences. They share zero UI structure beyond common components (nav chrome, notification bell, avatar menu). There is no single dashboard that toggles between "requester mode" and "operator mode."
app/
dashboard/
requester/
page.tsx ← Requester dashboard root
layout.tsx ← Requester-specific layout and nav
requests/
page.tsx ← Active requests with response counts
new/
page.tsx ← Post new request (primary CTA)
[id]/
page.tsx ← Single request detail + responses
history/
page.tsx ← Completed + expired requests
operator/
page.tsx ← Operator dashboard root
layout.tsx ← Operator-specific layout with subscription banner
feed/
page.tsx ← Active request feed for their radius
responses/
page.tsx ← Their submitted responses
bookings/
page.tsx ← Confirmed bookings
listing/
page.tsx ← Profile/truck editor
billing/
page.tsx ← Subscription managementWhen an operator submits a response, the requester sees the response count and a partially revealed operator identity. Full operator details (name, photo, truck info) are visible only after the requester clicks to review responses — not in the list view.
// In the request list, show:
{ responseCount: 3, firstRespondedAt: '2 hours ago' }
// Not: { operators: ['Fuego Street Kitchen', 'Coastal Eats', ...] }
// In the request detail (requester selected to review), show:
{ operators: [{ name, photo, cuisine, rating, proposedRate, note }] }🔴 REQUESTER INTERFACE VIOLATION
Found: Subscription/billing UI element in requester dashboard
This element must never appear in the requester interface.
The requester experience is free and must feel completely free.
Remove before proceeding.New requester → email verified → first request posted in under 3 minutes.
Onboarding steps:
No billing, no paywall, no upsell during onboarding.
Subscription Status Banner — always visible, at the top of every operator page, with varying urgency:
| Status | Banner appearance | Message |
|---|---|---|
trialing | Subtle info (blue) | "You're on a free trial — [N] days remaining. [Activate subscription]" |
active | Hidden (no banner when healthy) | — |
past_due | High-urgency warning (red) | "Your payment failed. Update your billing to keep responding to requests. [Fix now]" |
canceled | Warning (amber) until period end, then hard block | "Your subscription ends [date]. [Resubscribe]" |
incomplete | Blocking interstitial | "Complete your subscription setup to access the marketplace. [Continue setup]" |
Active Request Feed — filtered to their service radius. Cards show:
Pending Responses — responses they've submitted awaiting requester selection:
Confirmed Bookings — matches with status 'accepted':
Listing/Profile Editor — truck profile management:
Revenue Summary — confirmed bookings this month and trailing 90 days. No Stripe revenue — this is booking count × proposed rate from matches table.
Review History — reviews received and given.
Billing — link to Stripe Customer Portal or in-app subscription management.
New operator must complete before accessing the request feed:
The request feed is locked behind step 4. An operator who bounces during subscription setup must be re-engaged via email — build the drip from day one.
Route guards are enforced at the server, not the client. A requester hitting an operator route gets 403, not a redirect loop.
// app/dashboard/operator/layout.tsx
import { redirect } from 'next/navigation'
import { getSession, getUser } from '@/lib/auth'
export default async function OperatorDashboardLayout({
children,
}: {
children: React.ReactNode
}) {
const session = await getSession()
if (!session) {
redirect('/login?next=/dashboard/operator')
}
const user = await getUser(session.user.id)
if (user.role !== 'operator') {
// 403 — not a redirect to requester dashboard (that would silently succeed)
// A requester shouldn't be able to GET to an operator route at all
return new Response('Forbidden', { status: 403 })
}
return <>{children}</>
}
// app/dashboard/requester/layout.tsx — mirror pattern for requester routesFor API routes:
// lib/auth/guards.ts
export async function requireOperator(request: Request) {
const session = await getSessionFromRequest(request)
if (!session) return Response.json({ error: 'Unauthorized' }, { status: 401 })
const user = await getUser(session.user.id)
if (user.role !== 'operator') {
return Response.json({ error: 'Forbidden' }, { status: 403 })
}
return user
}
export async function requireRequester(request: Request) {
const session = await getSessionFromRequest(request)
if (!session) return Response.json({ error: 'Unauthorized' }, { status: 401 })
const user = await getUser(session.user.id)
if (user.role !== 'requester') {
return Response.json({ error: 'Forbidden' }, { status: 403 })
}
return user
}Shared components (Button, Card, Input, etc.) are fine. Shared UX patterns that blur role distinction are not.
Examples of what IS acceptable:
<Button> component<NotificationBell> component<AvatarMenu> componentExamples of what is NOT acceptable:
<Dashboard> component that renders different content based on user.role<RequestCard> that looks the same to a requester and an operator (they have different actions and info)user.role inside JSX to decide what to render at a high levelIf a component needs to know the user's role to render, it should be two separate components, not one component with role-based branching.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.