From 8eec29ad9040ee2402f6f4c724eb88ec3bae1189 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Thu, 14 May 2026 19:27:00 +0000 Subject: [PATCH 1/5] fix: correct infra paths in promote-to-uat workflow Fix hardcoded apps/groombook/... paths to apps/... per GRO-1274. Co-Authored-By: Paperclip --- .github/workflows/promote-to-uat.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/promote-to-uat.yml b/.github/workflows/promote-to-uat.yml index 083e013..22f5680 100644 --- a/.github/workflows/promote-to-uat.yml +++ b/.github/workflows/promote-to-uat.yml @@ -38,7 +38,7 @@ jobs: run: | echo "Updating UAT overlay image tags to: $TAG" cd /tmp/infra - UAT_KUST="apps/groombook/overlays/uat/kustomization.yaml" + UAT_KUST="apps/overlays/uat/kustomization.yaml" if [ ! -f "$UAT_KUST" ]; then echo "ERROR: UAT overlay not found at $UAT_KUST. Ensure GRO-427 has been completed." @@ -55,7 +55,7 @@ jobs: yq -i '(.images[] | select(.name == "ghcr.io/groombook/seed")).newTag = env(TAG)' "$UAT_KUST" # Update migrate Job name to include short SHA (immutable template fix) - MIGRATE_JOB="apps/groombook/base/migrate-job.yaml" + MIGRATE_JOB="apps/base/migrate-job.yaml" if [ -f "$MIGRATE_JOB" ]; then yq -i '.metadata.name = "migrate-schema-" + env(SHORT_SHA)' "$MIGRATE_JOB" yq -i '.metadata.annotations."groombook.app/deploy-version" = env(TAG)' "$MIGRATE_JOB" @@ -64,7 +64,7 @@ jobs: # Update seed Job name to include short SHA (immutable template fix) # NOTE: Do NOT update the image tag here — let the Kustomize images transformer # in the UAT overlay handle it via newTag. This avoids the immutable template issue. - SEED_JOB="apps/groombook/base/seed-job.yaml" + SEED_JOB="apps/base/seed-job.yaml" if [ -f "$SEED_JOB" ]; then yq -i '.metadata.name = "seed-test-data-" + env(SHORT_SHA)' "$SEED_JOB" yq -i '.metadata.annotations."groombook.app/deploy-version" = env(TAG)' "$SEED_JOB" @@ -81,7 +81,7 @@ jobs: git config user.name "groombook-engineer[bot]" git config user.email "3141748+groombook-engineer[bot]@users.noreply.github.com" git checkout -b "chore/update-uat-image-tags-${TAG}" - git add apps/groombook/overlays/uat/ apps/groombook/base/migrate-job.yaml apps/groombook/base/seed-job.yaml + git add apps/overlays/uat/ apps/base/migrate-job.yaml apps/base/seed-job.yaml git commit -m "chore: promote ${TAG} to UAT" git push -u origin "chore/update-uat-image-tags-${TAG}" From 7339d51acf295e04b6b0e12ccee23ec99c028b42 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Thu, 14 May 2026 19:40:59 +0000 Subject: [PATCH 2/5] fix: use window.location.origin as fallback for VITE_API_URL Vite bakes VITE_* vars at build time, so hardcoding a URL in .env.production breaks CI E2E which runs on localhost. Now falls back to the browser origin at runtime, which works correctly since nginx reverse-proxies /api to the local API container. Fixes GRO-1280. --- apps/web/src/lib/auth-client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/lib/auth-client.ts b/apps/web/src/lib/auth-client.ts index 6a9939a..7c5db68 100644 --- a/apps/web/src/lib/auth-client.ts +++ b/apps/web/src/lib/auth-client.ts @@ -1,7 +1,7 @@ import { createAuthClient } from "better-auth/react"; export const authClient = createAuthClient({ - baseURL: import.meta.env.VITE_API_URL ?? "", + baseURL: import.meta.env.VITE_API_URL || window.location.origin, }); export const { signIn, signOut, useSession, changePassword } = authClient; \ No newline at end of file From c2dd1dbf84e0624fc98812e759e7b974594308e8 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Thu, 14 May 2026 19:51:34 +0000 Subject: [PATCH 3/5] fix: add explicit ARG/ENV VITE_API_URL to Dockerfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without this, Vite sees VITE_API_URL as undefined (not empty string) at build time. The ?? operator only replaces null/undefined, not a missing var, so better-auth receives undefined — which it treats as a relative path and prepends window.location.origin at build time, resulting in the UAT URL being baked in. Explicitly setting ARG VITE_API_URL= (empty string) in the Dockerfile makes Vite see it as defined with empty value, so the || fallback fires at runtime. Fixes GRO-1280. --- apps/web/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index eb110fa..f551990 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -11,6 +11,8 @@ RUN pnpm install --frozen-lockfile # Build FROM deps AS builder +ARG VITE_API_URL= +ENV VITE_API_URL= COPY packages/types/ packages/types/ COPY apps/web/ apps/web/ RUN pnpm --filter @groombook/web build From 2398dabe3a21188569565835434f17f7f7c8c831 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Thu, 14 May 2026 19:51:47 +0000 Subject: [PATCH 4/5] fix: set VITE_API_URL env var in Build job Ensures Vite sees VITE_API_URL as an empty string (not undefined) during pnpm build, so the || window.location.origin fallback fires at runtime instead of baking in the UAT URL. Co-Authored-By: Paperclip --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6a8c173..1438d3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,6 +119,8 @@ jobs: run: pnpm install --frozen-lockfile - name: Build all packages + env: + VITE_API_URL: "" run: pnpm build docker: From 573869e5173e83120735a3c2565bcfd8c50c7bc5 Mon Sep 17 00:00:00 2001 From: "the-dogfather-cto[bot]" <269737991+the-dogfather-cto[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 20:16:22 +0000 Subject: [PATCH 5/5] fix: correct infra paths in promote-to-uat workflow (#414) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Promote dev → uat: ARIA modal fix + tip split atomicity (#335) * feat(GRO-785): validate tip split totals before marking invoice paid - PATCH /invoices/:id returns 400 when tipCents > 0 but no tip splits exist or splits don't sum to 100% - POST /invoices/:id/tip-splits now returns 400 (not 422) on validation failure via router-level ZodError handler Co-Authored-By: Paperclip * feat(GRO-786): add ARIA label attributes to Modal dialog component - Update Modal component to accept title and titleStyle props - Add role="dialog", aria-modal="true", and aria-labelledby attributes - Use useId() to generate stable ID for title heading association - Update all 4 Modal call sites (New/Edit Client, Add/Edit Pet, Log Grooming Visit, Permanently Delete Client) with title props - Delete modal passes titleStyle for red color on warning Co-Authored-By: Paperclip * fix(GRO-786): remove duplicate dialog role and restore focus trap - Remove role="dialog" and aria-modal="true" from outer backdrop div - Keep ARIA attributes only on inner dialog div (the actual modal) - Restore useEffect focus management: auto-focus first element, Tab cycle wrapping, Escape key handler, focus restore on close Co-Authored-By: Paperclip * fix(GRO-785): restore atomic tip split save in PATCH and fix error message - When body.tipSplits is provided in PATCH /invoices/:id, validate sum first then atomically replace existing splits (delete + insert) - When no incoming splits, validate existing DB splits with corrected message: "Tip splits are required when tip amount is greater than zero" (previously misleading "must sum to 100%" when no splits existed) Co-Authored-By: Paperclip * fix(GRO-785): address invoice tip split regression - Use body.tipCents ?? current.tipCents for validation condition so that simultaneous status=paid + tipCents=0 skip split validation - Use body.tipCents (now aliased as tipCents) instead of current.tipCents inside the atomic transaction for shareCents calculation - Add explicit check for empty tipSplits array with appropriate error message ("Tip splits are required when tip amount is greater than zero") before the sum-to-100% check - Destructure tipSplits out of body before spreading into update object to prevent it from leaking into the invoices table SET clause Co-Authored-By: Paperclip * fix(GRO-785): wrap tip split save + invoice update in single transaction Both tip split persistence (delete + insert) and the invoice PATCH update are now inside one db.transaction() block. If the invoice update fails after splits are written, the entire operation rolls back. Also removed unnecessary eslint-disable comment on _tipSplits. Co-Authored-By: Paperclip * fix(GRO-785): restore eslint-disable for intentionally unused _tipSplits var Co-Authored-By: Paperclip --------- Co-authored-by: Flea Flicker Co-authored-by: Paperclip Co-authored-by: the-dogfather-cto[bot] <269737991+the-dogfather-cto[bot]@users.noreply.github.com> * fix(gro-609): include stripePaymentIntentId in invoice list and wrap stats endpoint in try/catch - Add stripePaymentIntentId to the GET /invoices list query so the refund button renders when seed data includes a payment intent ID - Wrap /api/invoices/stats/summary in try/catch so errors return 200 with zero defaults instead of 5xx, preventing the Invoices page from crashing on mount for groomer-role sessions Parent: GRO-882 Grandparent: GRO-816 Co-Authored-By: Paperclip * fix(gro-609): add payment stats to admin dashboard (AppointmentsPage) - Fetch /api/invoices/stats/summary on mount and display Revenue/Outstanding/Refunds summary cards above the calendar view on /admin - Mirrors the same stats section already on /admin/invoices - Gracefully handles errors via try/catch on the stats endpoint Parent: GRO-882 Grandparent: GRO-816 Co-Authored-By: Paperclip * fix(GRO-890): populate stripePaymentIntentId on all paid seed invoices All paid invoices created by the seed script now get a deterministic stripePaymentIntentId of the form pi_test_seed_NNNNNN, unblocking the refund button conditional in Invoices.tsx:514 during UAT. Pending/draft invoices retain null as before. Co-Authored-By: Paperclip * fix(GRO-898): update CI to deploy on dev branch pushes Update the Update Infra Image Tags job condition to also trigger on pushes to the dev branch, not just main. Co-Authored-By: Paperclip * fix: correct infra paths in promote-to-uat workflow Remove 'groombook/' prefix from 4 path references in promote-to-uat.yml since the groombook/infra repo has apps/overlays/ and apps/base/ at the root, not under a groombook/ subdirectory. GRO-1274 --------- Co-authored-by: the-dogfather-cto[bot] <269737991+the-dogfather-cto[bot]@users.noreply.github.com> Co-authored-by: Flea Flicker Co-authored-by: Paperclip Co-authored-by: lint-roller-qa[bot] <269744346+lint-roller-qa[bot]@users.noreply.github.com> Co-authored-by: scrubs-mcbarkley-ceo[bot] <269735724+scrubs-mcbarkley-ceo[bot]@users.noreply.github.com> Co-authored-by: Test User Co-authored-by: groombook-engineer[bot] <3141748+groombook-engineer[bot]@users.noreply.github.com> Co-authored-by: Chris Farhood