Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/plans/BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,10 @@ W1 -> W3 -> W4-1 -> W4-3 -> W4-4 -> W5-2 -> W7-3 -> W7-4. W2 gates W4-3 and W5-2
- NEXT: after sign-in a real owner lands on `/schedule` (sample data). Send owners to `/manage`, students to their real bookings; the manage header must show the studio name, not "Tandava Yoga", and no sample notifications.
- NEXT: Turnstile fails in Taylor's Chrome ("Verification failed"). Check widget hostnames (tandavastudio.com, www) before enforcing captcha in Supabase Auth.
- LATER: `monthly` schedule rules are not generated (no UI creates them).

## Teacher portal first working slice (2026-10-18)

| Task | State | Acceptance / remaining gate |
|---|---|---|
| Assigned teacher schedule → roster → recorded attendance | Implemented and locally/CI validated, pending deployment | 00044: active assigned teacher/owner/admin only; narrow roster; existing check_in_booking authoritative window/status. Browser mocks cover failure, recovery, duplicate clicks and student denial. TEACH-01..15 and full DB suite passed locally and in PR100 CI; review/apply migration before deploying UI. |
| Teacher availability, substitutes, earnings and teacher profile | Deferred, explicitly unavailable in live UI | Demo unchanged. Separate real contracts needed; no false-save controls exposed live. |
20 changes: 20 additions & 0 deletions docs/plans/PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,23 @@ supabase start && supabase db reset && npm run test:db # throwaway local DB, n
- LP-9: Terms, Privacy and Refund Policy pages (operator Purafield Studio, contact hello@purafieldstudio.com, Texas law). Sign-up linked to /terms and /waiver, neither of which existed; it now links Terms and Privacy. Checkout entry points show what the buyer agrees to. The app footer no longer says "Oxatl Yoga" outside the demo.
- Refund defaults Taylor should confirm: 2-hour cancellation window unless the studio sets one; unused packs refundable within 14 days; no partial membership refunds.


## 2026-10-18: teacher schedule and attendance (local implementation)
- Baseline reconciled to remote main 7353a2a; obsolete launch-v1 checkout left untouched. Member cancellation failure/retry regression committed locally (82c613e).
- /teach and /teach/schedule now use assigned classes and roster RPCs for live sessions; demo retains existing sample screens. Live unsupported teacher routes show an explicit unavailable page, and no fake studio name, qualification or notification count is shown.
- Scope follows ROLE_ACCESS_CONTROL teacher own-schedule/own-roster/own-check-in and PRD-001 two-week default; earnings/substitution remain deferred. New DataProvider methods: getMyTeachingClasses, getTeachingRoster, checkInTeachingBooking. Narrow 00044 read RPCs require current assignment plus active teacher/owner/admin staff in the same studio. Roster returns only booking ID, display name and status. The check-in wrapper locks the class before assignment validation, then delegates to existing check_in_booking for status, cancellation and time-window enforcement. Existing general staff check-in permissions are unchanged.
- UI records attendance only after matching booking ID and checked_in response, prevents concurrent clicks, disables roster dismissal during writes, refreshes after ambiguous failures and offers explicit roster refresh. Waitlisted people cannot be checked in from the portal; attendance undo, substitution, payroll and bulk attendance are outside this slice.
- Added browser coverage for failed/successful check-in, duplicate clicks, persisted roster refresh, lost response recovery, schedule/roster errors, direct navigation to unavailable features and student route denial without teacher RPC requests.
- Added SQL TEACH-01..15 (assignment, same/cross-studio denial, disabled/unexpected roles, anon denial, time window, duplicate attendance, cancelled class). Included in test runner. **SQL tests NOT EXECUTED locally:** no PostgreSQL server installed/running. Migration00044 NOT APPLIED to any hosted database. Deployment is blocked on a clean database test run and explicit deployment authorization; mocks do not prove production RLS/function execution.
- Validation: TypeScript passed; 526 application tests and 32 script tests passed; final full browser suite 23/23 passed with 2 workers, including 390×844 mobile attendance and desktop recovery/sign-out. Earlier 5-worker run had one existing owner cancellation follow-up failure; focused 12/12 and final 23/23 reruns passed without modifying that flow. Production build/postbuild passed through E2E setup. Sign-out now waits for the backend and reports failures rather than navigating on an unconfirmed logout.

## 2026-10-18: upstream and hosted reconciliation (read-only)
- Fetched upstream: main still7353a2a; no open PRs. Local82c613e and209778a remain unpushed. GitHub's Vercel status is success for deployment HrDS9DXSLc6xvqZfgxUhi7s1jaW8. Public /teach serves HTTP200 SPA shell (main--tUT3jD_.js); authenticated teacher behavior cannot be inferred from that shell.
- Supabase CLI function inventory for tandava-prod returned403 (connected account lacks privileges). No source download, secret access or production query followed. Vercel connector403 and scoped CLI inspect could not resolve the deployment; GitHub/public HTTP evidence is available, direct deployment metadata remains unavailable.
- Latest Stripe smoke39131170968 (Oct18) still fails: Missing SUPABASE_SERVICE_ROLE_KEY. No dispatch or credentials configured.
- Prepared TEACHER_ROLLOUT.md, exact transactional apply-00044.sql bundle, and read-only verify-00043-00044.sql. Migration00043/function deployment status remains unverified;00044 is local only. Full SQL validation and explicit rollout approval remain required.

## 2026-10-18: database and PR validation completed
- PostgreSQL16 installed after explicit approval. Fresh isolated Tandava cluster applied every migration through00044 and completed the full SQL suite:251 PASS lines, including TEACH-01..15, payment/refund/idempotency tests, attribution39 blocks, last-seat and last-credit races. Temporary socket-only server stopped and data removed.
- Initial combined cross-app runner produced a false PAY-01 failure: TeacherTree's test creates cluster-global service_role without BYPASSRLS; Tandava's stub preserved it on duplicate-object. Separate fresh clusters per app restore the intended test environment without changing role permissions. No production/payment-code defect was found, and the temporary diagnostic change was reverted.
- PR100 CI run39176946297 passed all four jobs: migrations+SQL, Edge Functions typecheck, browser E2E, and web typecheck/lint/test/build. Local and CI database gates are now complete; prior notes that SQL was unexecuted describe earlier state. No hosted migration or deployment has been performed.
39 changes: 39 additions & 0 deletions docs/plans/TEACHER_ROLLOUT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Teacher attendance rollout packet — 2026-10-18

Prepared only. No hosted SQL, deployment, credentials, Stripe events, or workflow runs were changed.

## Source and current evidence

- Remote `main` remains `7353a2a16be3d5f19f19231d587677011264897a`; no open PRs when rechecked. Isolated local branch adds member regression `82c613e` and teacher implementation `209778a`.
- GitHub Vercel commit status reports success for main at deployment `HrDS9DXSLc6xvqZfgxUhi7s1jaW8`: https://vercel.com/tayloroneal-1467s-projects/tandava/HrDS9DXSLc6xvqZfgxUhi7s1jaW8 . Public `/teach` returns HTTP200 SPA HTML; this alone does not verify authenticated operation.
- Existing Supabase CLI authentication returns403 for `functions list --project-ref mkaixgjwakfufmmwembn`. No source download was attempted after denial. Migration00043 and deployed function versions remain unverified, not assumed absent.
- Migration00044 exists only locally; its three teacher RPCs are not on upstream main. New teacher frontend has not been deployed.
- Local checks: TypeScript and focused ESLint pass;526 application+32 script tests pass;23 browser tests pass with2 workers (including390×844 teacher attendance). Full SQL suite now passes in a fresh isolated PostgreSQL16 cluster:251 PASS lines including all15 teacher cases; PR100 CI also passes all four jobs. Temporary server/data were cleaned up.

## Reviewable artifacts

| Artifact | Purpose |
|---|---|
| `scripts/db/prod/verify-00043-00044.sql` | Read-only function/table presence, grants, search paths and definition fingerprints; no user rows or secret values |
| Existing `scripts/db/prod/apply-00043.sql` | Existing owner class-cancellation migration bundle, only if owner verification proves needed |
| `scripts/db/prod/apply-00044.sql` | Exact migration00044 wrapped in BEGIN/COMMIT; execute only after DB validation and deployment approval |
| `supabase/tests/024_teacher_attendance.test.sql` |15 assignment/authorization/attendance cases, included in full test runner |
| `supabase/functions/cancel-class/index.ts` and `stripe-webhook/index.ts` | Existing main owner cancellation/refund code; separate from teacher RPC feature |

## Dependency order after approval

1. Local clean-database migration+full SQL suite completed successfully in a disposable PostgreSQL16 cluster; PR100 CI independently passed. Preserve existing production migration history; earlier production SQL was manually applied, so do not issue blind `db push` or migration repair.
2. Project owner runs the read-only verification query and supplies function metadata (name/version/JWT mode) for `cancel-class` and `stripe-webhook`. Compare deployed source/definitions to main. Presence alone is not proof of matching implementation. Any absent or drifted prerequisite must be reviewed before writing.
3. If00043 is not installed, review and apply its established bundle first. Deploy matching `cancel-class` with JWT verification and `stripe-webhook` with Stripe signature verification (no JWT). Owner confirms webhook subscriptions include `refund.updated`, `refund.failed`, `charge.refund.updated`. These are state changes requiring approval. Do not run the broad deploy-functions script merely to update these two functions.
4. Review and apply00044 after its tests pass. The teacher feature requires no Edge Function deployment, new credential, or global role-permission change. New RPCs require active assigned teacher/owner/admin; existing general staff check-in RPC permissions remain unchanged.
5. Publish/review the local commits, then release the frontend after SQL is confirmed. Neither push nor merge nor deployment is authorized by this packet.
6. In an approved sandbox, verify assigned teacher sign-in → class list → roster → single committed check-in → refresh; verify student/unassigned/inactive callers cannot use new paths. Owner price/schedule and member booking flows remain part of the regression set.

## Payment proof remains separate

Latest scheduled Stripe smoke (2026-10-18): https://github.com/TaylorONeal/tandava/actions/runs/39131170968 . It fails before the flow starts because `SUPABASE_SERVICE_ROLE_KEY` is missing, mapped from GitHub environment `stripe-smoke` secret `SMOKE_SUPABASE_SECRET_KEY`. Also required: `SMOKE_STRIPE_TEST_KEY`, `SMOKE_STRIPE_WEBHOOK_SECRET` (presence unverified). Supply values directly through GitHub environment settings, never chat. This workflow mutates hosted sandbox test data and requires authorization before manual dispatch. A passing mock flow does not prove checkout/refund/webhook behavior live.


## Validated PR

https://github.com/TaylorONeal/tandava/pull/100 — CI https://github.com/TaylorONeal/tandava/actions/runs/39176946297 passed all jobs. PostgreSQL tests must use a fresh cluster per app: role objects are cluster-global even when app databases differ. The initial shared-cluster fixture mismatch was corrected by isolating test clusters, not by changing production roles or payment logic.
47 changes: 47 additions & 0 deletions e2e/member-bookings.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { expect, test } from "@playwright/test";

test("member sees their bookings and cancels only after server confirmation", async ({ page }) => {
const user = { id: "0a000000-0000-0000-0000-00000000000b", email: "member@test.dev", aud: "authenticated", role: "authenticated", app_metadata: {}, user_metadata: {} };
await page.addInitScript((user) => localStorage.setItem("sb-127-auth-token", JSON.stringify({
access_token: "e2e-token", refresh_token: "e2e-refresh", token_type: "bearer", expires_in: 3600,
expires_at: Math.floor(Date.now() / 1000) + 3600, user,
})), user);
let cancelled = false;
let rejectCancellation = true;
const booking = {
booking_id: "22222222-2222-2222-2222-222222222222", occurrence_id: "33333333-3333-3333-3333-333333333333",
status: "confirmed", starts_at: new Date(Date.now() + 86400000).toISOString(),
ends_at: new Date(Date.now() + 90000000).toISOString(), is_cancelled: false,
offering_name: "Member Vinyasa", teacher_name: "Sam", room: "Main", location_name: "Downtown",
studio_name: "Test Studio", studio_slug: "test-studio", studio_timezone: "America/Chicago", cancellation_minutes: 120,
};
await page.route("http://127.0.0.1:54321/**", async route => {
const req = route.request();
const url = req.url();
const headers = { "access-control-allow-origin": "*", "access-control-allow-headers": "*", "access-control-allow-methods": "*" };
const json = (body: unknown, status = 200) => route.fulfill({ status, headers, contentType: "application/json", body: JSON.stringify(body) });
if (req.method() === "OPTIONS") return route.fulfill({ status: 204, headers });
if (url.includes("/rpc/get_my_effective_role")) return json("student");
if (url.includes("/rest/v1/profiles")) return json({ ...user, first_name: "Test", last_name: "Member" });
if (url.includes("/rpc/get_my_bookings")) return json(cancelled ? [] : [booking]);
if (url.includes("/rpc/cancel_booking")) {
expect(req.postDataJSON()).toEqual({ p_booking_id: booking.booking_id });
if (rejectCancellation) return json({ message: "Please retry cancellation" }, 400);
cancelled = true;
return json({ ...booking, status: "cancelled" });
}
if (url.includes("/auth/v1/user")) return json(user);
return json([]);
});
await page.goto("/my-schedule");
await expect(page.getByRole("heading", { name: "Member Vinyasa" })).toBeVisible();
await expect(page.getByText("Power Vinyasa Flow", { exact: true })).toHaveCount(0);
await page.getByRole("button", { name: "Cancel", exact: true }).click();
await page.getByRole("button", { name: "Cancel Booking", exact: true }).click();
await expect(page.getByText("Please retry cancellation").first()).toBeVisible();
expect(cancelled).toBe(false);
rejectCancellation = false;
await page.getByRole("button", { name: "Cancel Booking", exact: true }).click();
await expect(page.getByRole("heading", { name: "No upcoming bookings" })).toBeVisible();
expect(cancelled).toBe(true);
});
Loading
Loading