Skip to content

Epic E12: Policies, safety, and reliability#106

Merged
Taleef7 merged 3 commits intomainfrom
copilot/implement-epic-e12-policies
Feb 26, 2026
Merged

Epic E12: Policies, safety, and reliability#106
Taleef7 merged 3 commits intomainfrom
copilot/implement-epic-e12-policies

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 25, 2026

Implements all four tasks under Epic E12 — foundational trust, safety, and operational visibility features required before public launch.

Summary

  • Public /policies page with all locked MVP policy sections
  • Public /tutor/conduct code-of-conduct page with required acknowledgement in the tutor application form
  • Admin-only /admin/audit audit log view
  • Admin-only /admin/analytics operational health dashboard

Changes

T12.1 — /policies page (app/policies/page.tsx)

  • Public static page (no auth) with 5 sections matching locked MVP decisions from docs/MVP.md §5: reschedule (24h cutoff, exceptions), no-show table (student/tutor/late-join outcomes), refund/expiry (no carryover, admin discretion), package terms, privacy basics

T12.2 — Tutor code of conduct (app/tutor/conduct/page.tsx)

  • Public page covering 6 conduct areas: punctuality, session quality, communication, privacy, quality expectations, incidents
  • Required acknowledgement checkbox added to tutor profile form; uses z.boolean().refine() so Zod surfaces the custom error message when unchecked
  • Checkbox (id="conductAcknowledged") is bound via htmlFor on the label text; the "View Code of Conduct →" link is placed outside the <label> so clicking it does not accidentally toggle the checkbox

T12.3 — Admin audit log (app/admin/audit/page.tsx)

  • Admin-only; queries existing audit_logs table (created in E5 migration) for 200 most recent events, newest-first
  • Renders actor name, human-readable action label (10 mapped, aligned with actual action strings in codebase: payment_marked_paid, payment_marked_rejected, tutor_approved, tutor_approval_revoked, tutor_assigned, tutor_reassigned, sessions_generated, session_rescheduled, session_status_updated, match_details_updated), entity type, truncated UUID, details summary; timestamps in PKT
  • Non-primitive detail values (e.g. schedule_pattern objects) are serialized with JSON.stringify and capped at 80 characters so nested details remain readable

T12.4 — Admin analytics dashboard (app/admin/analytics/page.tsx)

  • force-dynamic page with 7 parallel count queries (no row data transferred): active students, active tutors, upcoming sessions (next 7d), missed sessions (last 7d), unmarked past sessions, pending payments, pending tutor applications
  • Throws an explicit error if any Supabase count query fails, preventing silent zero counts from masking outages
  • Attention metrics (unmarked/pending) rendered in amber/orange with links to the relevant admin page

Supporting

  • Admin nav + dashboard cards updated to include Audit Log and Analytics
  • lib/validators/tutor.ts updated with conductAcknowledged field using z.boolean().refine()
  • README updated to "after E12" state

Testing

  • Verified locally
  • Checked key flows manually

Notes

  • audit_logs table already existed from the E5 packages/payments migration — no new migration needed
  • Conduct page is intentionally public (no auth) so it can be linked before a tutor has an account
  • AUDIT_ACTION_LABELS keys are aligned with the actual action strings written throughout the codebase (verified by grepping all audit_logs insert sites)

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

T12.1: Full /policies page (reschedule, no-show, refund/expiry, package terms, privacy)
T12.2: /tutor/conduct code of conduct page + required acknowledgement checkbox on tutor form
T12.3: /admin/audit audit log page (recent 200 events, human-readable labels, actor/entity/details)
T12.4: /admin/analytics dashboard (7 metric cards: active students/tutors, upcoming/missed/unmarked sessions, pending payments/tutors)
- Admin nav + dashboard updated with Audit Log and Analytics links
- README updated to reflect E12 features

Closes #78, Closes #79, Closes #80, Closes #81, Closes #77

Co-authored-by: Taleef7 <89072337+Taleef7@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement epic E12 for policies, safety, and reliability Epic E12: Policies, safety, and reliability Feb 25, 2026
@Taleef7 Taleef7 marked this pull request as ready for review February 25, 2026 23:04
Copilot AI review requested due to automatic review settings February 25, 2026 23:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements Epic E12 launch-readiness features by adding public-facing policy/safety pages and admin operational visibility pages, plus wiring the tutor conduct acknowledgement into the tutor profile form.

Changes:

  • Add public /policies page and public /tutor/conduct code-of-conduct page.
  • Require tutors to acknowledge the code of conduct in the tutor profile/application form via Zod validation + UI checkbox.
  • Add admin-only /admin/audit (recent audit events) and /admin/analytics (count-based health metrics), plus navigation entry points.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
lib/validators/tutor.ts Adds conductAcknowledged requirement to tutor profile Zod schema.
app/tutor/profile/TutorProfileForm.tsx Adds conduct acknowledgement checkbox linking to /tutor/conduct.
app/tutor/conduct/page.tsx New public tutor code-of-conduct page content.
app/policies/page.tsx Replaces TODO with full public policies page content.
app/admin/page.tsx Adds admin dashboard cards for Audit Log and Analytics.
app/admin/layout.tsx Adds nav links to /admin/audit and /admin/analytics.
app/admin/audit/page.tsx New admin audit log page querying audit_logs and rendering recent events.
app/admin/analytics/page.tsx New admin analytics dashboard with parallel count queries and metric cards.
README.md Updates “after E11” to “after E12” and documents new E12 features.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/admin/audit/page.tsx
Comment on lines +104 to +108
const detailsStr = log.details
? Object.entries(log.details)
.map(([k, v]) => `${k}: ${String(v)}`)
.join(' · ')
: '—'
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

detailsStr stringifies values with String(v), which turns objects into [object Object] (e.g. match_details_updated can include a schedule_pattern object). This makes the “Details” column lose the most useful context. Consider formatting non-primitive values with JSON.stringify (with a small length cap) so nested details remain readable.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7e81337. Non-primitive values (objects like schedule_pattern) are now serialized with JSON.stringify and capped at 80 characters with a trailing , so nested details remain readable instead of showing [object Object].

.select('tutor_user_id', { count: 'exact', head: true })
.eq('approved', false),
])

Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The analytics counts ignore query errors: if any of these Supabase requests fail, count becomes null and the UI silently shows 0, which can mask outages and lead to incorrect operational decisions. Consider checking each response’s error and rendering an explicit error state (or throwing) when any metric query fails.

Suggested change
const firstErrorResult =
activeStudents.error ||
activeTutors.error ||
upcomingSessions.error ||
missedSessions.error ||
unmarkedSessions.error ||
pendingPayments.error ||
pendingTutors.error
if (firstErrorResult) {
throw new Error(
`Failed to load admin analytics metrics: ${
typeof firstErrorResult.message === 'string'
? firstErrorResult.message
: 'Unknown Supabase error'
}`,
)
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7e81337. After Promise.all resolves, the page now checks activeStudents.error || activeTutors.error || … and throws an explicit Error if any query failed. Next.js will render the nearest error boundary rather than silently displaying zeros.

Comment thread lib/validators/tutor.ts Outdated
Comment on lines +30 to +32
conductAcknowledged: z.literal(true, {
error: 'You must read and agree to the CorvEd Tutor Code of Conduct',
}),
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

z.literal(true, { error: ... }) uses an unsupported option key (error). This will either fail TypeScript compilation or silently fall back to Zod’s default message, so the form won’t show the intended validation error. Use a supported option (e.g., required_error/invalid_type_error where applicable) or switch to z.boolean().refine(v => v, { message: ... }) to guarantee the custom message is surfaced when unchecked.

Suggested change
conductAcknowledged: z.literal(true, {
error: 'You must read and agree to the CorvEd Tutor Code of Conduct',
}),
conductAcknowledged: z
.boolean()
.refine((v) => v, {
message: 'You must read and agree to the CorvEd Tutor Code of Conduct',
}),

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7e81337. Switched to z.boolean().refine((v) => v, { message: '…' }) so the custom validation message is correctly surfaced when the checkbox is unchecked.

Comment thread app/tutor/profile/TutorProfileForm.tsx Outdated
Comment on lines +327 to +344
<label className="flex items-start gap-3 cursor-pointer">
<input
type="checkbox"
aria-required="true"
aria-invalid={errors.conductAcknowledged ? 'true' : 'false'}
{...register('conductAcknowledged')}
className="mt-0.5 h-4 w-4 rounded border-zinc-300 text-indigo-600 focus:ring-indigo-500"
/>
<span className="text-sm text-zinc-700 dark:text-zinc-300">
I have read and agree to the{' '}
<Link
href="/tutor/conduct"
target="_blank"
rel="noopener noreferrer"
className="font-semibold text-indigo-600 hover:text-indigo-700 underline underline-offset-2 dark:text-indigo-400 dark:hover:text-indigo-300"
>
CorvEd Tutor Code of Conduct
</Link>{' '}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the /tutor/conduct link is inside the <label>, clicking the link will also toggle the checkbox (label click behavior). This makes it easy to accidentally “acknowledge” without intending to, and is confusing for users. Consider using htmlFor with an id on the checkbox and keeping the link outside the label (or stop propagation on the link) so only direct checkbox/label text clicks toggle it.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7e81337. The link is now outside the <label> element. The checkbox has an explicit id="conductAcknowledged" and the label uses htmlFor="conductAcknowledged", so only clicking the checkbox or its label text toggles it — clicking "View Code of Conduct →" opens the conduct page without touching the checkbox.

Comment thread app/admin/audit/page.tsx
Comment on lines +10 to +22
// Human-readable labels for known audit actions
const AUDIT_ACTION_LABELS: Record<string, string> = {
payment_marked_paid: '💳 Payment marked paid',
payment_rejected: '❌ Payment rejected',
tutor_approved: '✅ Tutor approved',
tutor_approval_revoked: '🚫 Tutor approval revoked',
tutor_assigned: '🎓 Tutor assigned to request',
tutor_reassigned: '🔄 Tutor reassigned',
sessions_generated: '📅 Sessions generated',
session_rescheduled: '📅 Session rescheduled',
session_status_updated: '📝 Session status updated',
meet_link_updated: '🔗 Meet link updated',
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AUDIT_ACTION_LABELS doesn’t match the action strings that are actually written to audit_logs elsewhere (e.g. actions include payment_marked_rejected and match_details_updated, but this map has payment_rejected and no entry for match_details_updated). As a result, the audit log UI will show raw action keys for common events instead of the intended human-readable labels. Align the keys in this map with the action values inserted throughout the codebase (or standardize the inserted action names).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 7e81337. The label map now uses the actual action strings written to audit_logs throughout the codebase:

  • payment_marked_rejected (was payment_rejected)
  • match_details_updated (was meet_link_updated)

The map now covers all 10 real action strings: payment_marked_paid, payment_marked_rejected, tutor_approved, tutor_approval_revoked, tutor_assigned, tutor_reassigned, sessions_generated, session_rescheduled, session_status_updated, match_details_updated.

@Taleef7
Copy link
Copy Markdown
Owner

Taleef7 commented Feb 26, 2026

@copilot apply changes based on the comments in this thread

- Align AUDIT_ACTION_LABELS with actual action strings (payment_marked_rejected, match_details_updated; remove non-existent payment_rejected, meet_link_updated)
- Format non-primitive detail values with JSON.stringify (80-char cap) instead of String(), so nested objects like schedule_pattern remain readable
- Throw explicit error when any analytics metric query fails instead of silently showing 0
- Switch conductAcknowledged from z.literal(true) to z.boolean().refine() so custom error message is correctly surfaced
- Fix label/link UX: move 'View Code of Conduct' link outside <label> so clicking it doesn't accidentally toggle the checkbox

Co-authored-by: Taleef7 <89072337+Taleef7@users.noreply.github.com>
@Taleef7 Taleef7 merged commit 05f48a1 into main Feb 26, 2026
1 check passed
@Taleef7 Taleef7 deleted the copilot/implement-epic-e12-policies branch February 26, 2026 00:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants