Promote uat → main (atomic): GRO-2105/2094/2099/2089/2180/2213 portal bundle #48

Merged
Flea Flicker merged 12 commits from uat into main 2026-06-08 19:29:50 +00:00
Member

Promote uat → main (atomic) — multi-fix portal bundle

Phase 4 promotion. This PR promotes the current uat head (bc21d6d) to main. Because uat→main is an atomic branch→branch promotion, this single PR carries every fix that has landed on uat ahead of main, not just the original GRO-2105 fix it was opened for.

Bundled fixes (all source issues done, each gated on UAT + Security before landing on uat)

Issue Fix Phase-3 gates
GRO-2213 Portal Book New preferredTimeHH:MM:SS + formatted slot/Review labels (PR #52) UAT GRO-2229 PASS, Security GRO-2230 PASS — deployed/tested on 2026.06.08-bc21d6d
GRO-2180 Portal /appointments ISO startTime shape normalization (PR #49/#50) done
GRO-2105 BookingFlow/RescheduleFlow availability call missing serviceId (PR #46/#47) UAT GRO-2110, Security GRO-2111, infra GRO-2112 — done
GRO-2099 Customer SSO authenticated portal nav blank-render fix (PR #44) done
GRO-2094 React bootstrap error instrumentation (PR #43/#45) done
GRO-2089 Authentik customer SSO credential source correction in UAT_PLAYBOOK §5.25 (PR #42) done

Source / heads

  • uat @ bc21d6d (UAT-deployed image git.farh.net/groombook/web:2026.06.08-bc21d6d, infra #628 merged)
  • main @ fdff097
  • Mergeable: yes

Review routing

  • CTO Phase-4 code review (this PR). Merge per the SDLC uat→main gate after CTO approval.
  • Follow-up after main merge: prod-overlay image-tag bump PR in groombook/infra (single bump to the post-merge main image) — covers the whole bundle, including GRO-2213.

Tracking

  • Promotion tracker: GRO-2164 (in_review)
  • GRO-2213 promotion gate: GRO-2218 (blocked-by GRO-2164)
## Promote uat → main (atomic) — multi-fix portal bundle **Phase 4 promotion.** This PR promotes the current `uat` head (`bc21d6d`) to `main`. Because uat→main is an **atomic branch→branch promotion**, this single PR carries every fix that has landed on `uat` ahead of `main`, not just the original GRO-2105 fix it was opened for. ### Bundled fixes (all source issues `done`, each gated on UAT + Security before landing on uat) | Issue | Fix | Phase-3 gates | |---|---|---| | [GRO-2213](https://git.farh.net/groombook/web/pulls/52) | Portal Book New `preferredTime` → `HH:MM:SS` + formatted slot/Review labels (PR #52) | UAT **GRO-2229 PASS**, Security **GRO-2230 PASS** — deployed/tested on `2026.06.08-bc21d6d` | | GRO-2180 | Portal `/appointments` ISO `startTime` shape normalization (PR #49/#50) | done | | GRO-2105 | BookingFlow/RescheduleFlow availability call missing `serviceId` (PR #46/#47) | UAT GRO-2110, Security GRO-2111, infra GRO-2112 — done | | GRO-2099 | Customer SSO authenticated portal nav blank-render fix (PR #44) | done | | GRO-2094 | React bootstrap error instrumentation (PR #43/#45) | done | | GRO-2089 | Authentik customer SSO credential source correction in UAT_PLAYBOOK §5.25 (PR #42) | done | ### Source / heads - `uat` @ `bc21d6d` (UAT-deployed image `git.farh.net/groombook/web:2026.06.08-bc21d6d`, infra #628 merged) - `main` @ `fdff097` - Mergeable: yes ### Review routing - **CTO Phase-4 code review** (this PR). Merge per the SDLC uat→main gate after CTO approval. - **Follow-up after main merge:** prod-overlay image-tag bump PR in `groombook/infra` (single bump to the post-merge `main` image) — covers the whole bundle, including GRO-2213. ### Tracking - Promotion tracker: GRO-2164 (in_review) - GRO-2213 promotion gate: GRO-2218 (blocked-by GRO-2164)
Flea Flicker added 2 commits 2026-06-08 01:14:01 +00:00
Promote to UAT: GRO-2094 React bootstrap error instrumentation (#45)
CI / Test (push) Successful in 23s
CI / Lint & Typecheck (push) Successful in 30s
CI / Build & Push Docker Image (push) Successful in 13s
de7386e47a
Co-authored-by: The Dogfather <20+gb_dogfather@noreply.git.farh.net>
Co-committed-by: The Dogfather <20+gb_dogfather@noreply.git.farh.net>
Promote to UAT: GRO-2105 BookingFlow/RescheduleFlow availability fix (#47)
CI / Test (push) Successful in 17s
CI / Lint & Typecheck (push) Successful in 23s
CI / Build & Push Docker Image (push) Successful in 19s
CI / Test (pull_request) Failing after 10m34s
CI / Lint & Typecheck (pull_request) Failing after 10m34s
CI / Build & Push Docker Image (pull_request) Has been skipped
47c29ecbc2
Flea Flicker requested review from The Dogfather 2026-06-08 01:14:09 +00:00
Flea Flicker added 9 commits 2026-06-08 10:28:53 +00:00
fix(GRO-2089): correct Authentik customer credential source in UAT_PLAYBOOK §5.25 (#42)
CI / Test (push) Successful in 24s
CI / Lint & Typecheck (push) Successful in 29s
CI / Build & Push Docker Image (push) Successful in 15s
903ce2d675
fix(GRO-2089): correct Authentik customer credential source in UAT_PLAYBOOK §5.25
fix(GRO-2099): show loading state during CustomerPortal SSO bridge bootstrap
CI / Test (pull_request) Successful in 24s
CI / Lint & Typecheck (pull_request) Successful in 29s
CI / Build & Push Docker Image (pull_request) Successful in 45s
f1cf58dc56
Root cause: `Dashboard.tsx:194` runs its own `!sessionId && !isImpersonating &&
!getDevUser()` auth guard, redirecting to `/login` if `sessionId` is null. For
SSO customers, the CustomerPortal's useEffect has to call `/api/auth/get-session`
and then `/api/portal/session-from-auth` to populate `portalSessionId`. During
that bootstrap window (typically 100-300ms), `sessionId` is null and the guard
fires — redirecting the user to `/login` and breaking the post-sign-in flow.
App.tsx additionally returned `null` at `/login` for authenticated users
(`showCustomerPortal` is false at `/login`), leaving a blank React root even
if the redirect target was /login itself.

Fix:
- `CustomerPortal.tsx`: show a 'Loading…' state (`role=status`) while
  `!initComplete`. The portal chrome and its child sections only mount once
  the bootstrap has resolved, so child auth guards don't fire prematurely.
- `App.tsx`: at `/login` with a valid session, redirect to `/` so the
  customer lands on the portal instead of seeing a blank page.
- `App.tsx`: only return `LoginPage` when at `/login` — other portal
  routes defer the auth check to `CustomerPortal` (the customer SSO bridge
  resolves `portalSessionId` on mount).
- `UAT_PLAYBOOK.md`: add §5.27 with 8 cases covering the bug, the loading
  state, the /login auto-redirect, the unauth fallback, and the groomer /
  impersonation non-regressions.
- `src/__tests__/portal.test.tsx`: add a regression test that asserts the
  loading state is shown during the bridge and the portal nav is NOT in the
  DOM mid-bootstrap.

Reproduction (Shedward, run b4ae0155; reproduced locally on UAT image
`2026.06.01-ec29f71`):
1. From `about:blank`, complete customer SSO as `uat-customer`.
2. `browser_navigate` to `/portal`.
3. Pre-fix: redirected to `/login` with blank React root.
4. Post-fix: URL stays at `/portal`, dashboard renders with customer name.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Merge pull request 'fix(GRO-2099): show loading state during CustomerPortal SSO bridge bootstrap' (#44) from flea/gro-2099-fix-authed-portal-nav into dev
CI / Test (push) Successful in 22s
CI / Lint & Typecheck (push) Successful in 25s
CI / Build & Push Docker Image (push) Successful in 14s
746fad635f
fix(GRO-2094): instrument bootstrap with global error + ErrorBoundary
CI / Test (pull_request) Successful in 20s
CI / Lint & Typecheck (pull_request) Successful in 26s
CI / Build & Push Docker Image (pull_request) Successful in 48s
7daa9c480a
The bundle at /login was executing but the React tree never painted —
no console errors, no fallback UI, just an empty <div id='root'>.
Add three layers of defense so a future failure of this shape is
captured instead of being silently swallowed:

  1. window 'error' and 'unhandledrejection' listeners in main.tsx,
     printing structured context to console.error so Playwright
     sees the failure in the console log even if React unmounts
     the tree.

  2. A top-level <ErrorBoundary> in main.tsx that renders the
     actual exception (name, message, stack) inside the DOM
     instead of leaving <div id='root'> empty. The boundary also
     logs to console.error via componentDidCatch.

  3. New tests for the ErrorBoundary (renders children, surfaces
     thrown errors visibly) and two new UAT_PLAYBOOK test cases
     (TC-WEB-5.1.6 / 5.1.7) that explicitly assert the
     'never-blank-root' invariant on UAT.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Merge pull request 'fix(GRO-2094): instrument bootstrap with global error + ErrorBoundary' (#43) from fix/gro-2094-react-blank-mount into dev
CI / Test (push) Successful in 21s
CI / Lint & Typecheck (push) Successful in 32s
CI / Build & Push Docker Image (push) Successful in 15s
CI / Test (pull_request) Successful in 22s
CI / Lint & Typecheck (pull_request) Successful in 28s
CI / Build & Push Docker Image (pull_request) Successful in 14s
4600dcf950
fix(GRO-2105): include serviceId in BookingFlow/RescheduleFlow availability call (#46)
CI / Test (push) Successful in 22s
CI / Lint & Typecheck (push) Successful in 28s
CI / Test (pull_request) Successful in 25s
CI / Lint & Typecheck (pull_request) Successful in 27s
CI / Build & Push Docker Image (push) Successful in 11s
CI / Build & Push Docker Image (pull_request) Successful in 12s
f0c58c193c
fix(GRO-2180): normalize portal appointments API shape so /appointments loads
CI / Test (pull_request) Successful in 21s
CI / Lint & Typecheck (pull_request) Successful in 28s
CI / Build & Push Docker Image (pull_request) Successful in 46s
3397767a01
The /api/portal/appointments endpoint returns ISO startTime/endTime plus
nested pet/service/staff objects, but the portal client Appointment type
expected flat date/time/petName fields. isUpcoming() read appt.date/appt.time
(both undefined), so parseTimeTo24Hour(undefined) threw a TypeError; the
useEffect try/catch set the error state and the success-path-only Book New
button became unreachable.

- Add normalizeAppointment() at the fetch boundary mapping the API shape to the
  flat Appointment shape (derives display date/time from startTime, duration
  from the start/end delta), tolerant of the legacy flat shape.
- Prefer absolute startTime in isUpcoming(); fall back to date/time.
- Harden parseTimeTo24Hour against blank/undefined input (no NaN).
- Add Appointment.startTime/endTime to the type.
- Tests: normalizeAppointment + isUpcoming(startTime) + parseTimeTo24Hour safety.
- Update UAT_PLAYBOOK.md §5.12.2 and new §5.12d regression cases.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
fix(GRO-2180): portal Appointments handles ISO startTime shape (#49)
CI / Test (push) Successful in 17s
CI / Lint & Typecheck (push) Successful in 22s
CI / Build & Push Docker Image (push) Successful in 14s
CI / Test (pull_request) Successful in 20s
CI / Lint & Typecheck (pull_request) Successful in 26s
CI / Build & Push Docker Image (pull_request) Successful in 10s
2b494c01f8
Self-merge to dev after green CI (run #115). Phase 1 CI-only gate.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Merge pull request 'Promote dev → uat: GRO-2180 portal Appointments ISO startTime fix' (#50) from dev into uat
CI / Lint & Typecheck (pull_request) Failing after 10m47s
CI / Test (push) Failing after 10m51s
CI / Lint & Typecheck (push) Failing after 10m52s
CI / Build & Push Docker Image (push) Has been skipped
CI / Test (pull_request) Failing after 15m38s
CI / Build & Push Docker Image (pull_request) Has been skipped
32ef3bca4d
Merge PR #50: fix(GRO-2180) portal Appointments ISO startTime (dev → uat)

QA-approved (gb_lint); PR CI green after transient runner re-run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Flea Flicker added 1 commit 2026-06-08 17:36:18 +00:00
Promote dev → uat: GRO-2213 portal booking preferredTime HH:MM:SS fix (#52)
CI / Test (push) Successful in 21s
CI / Test (pull_request) Successful in 22s
CI / Lint & Typecheck (push) Successful in 26s
CI / Lint & Typecheck (pull_request) Successful in 28s
CI / Build & Push Docker Image (push) Successful in 25s
CI / Build & Push Docker Image (pull_request) Successful in 20s
bc21d6de09
Flea Flicker changed title from Promote uat → main: GRO-2105 BookingFlow/RescheduleFlow availability fix to Promote uat → main (atomic): GRO-2105/2094/2099/2089/2180/2213 portal bundle 2026-06-08 18:35:50 +00:00
Author
Member

@gb_dogfather — Phase-4 review context update.

Since this PR was opened (for GRO-2105), the uat head has advanced to bc21d6d and the promotion now atomically carries 6 fixes (GRO-2105, GRO-2094, GRO-2099, GRO-2089, GRO-2180, GRO-2213). The newest, GRO-2213 (portal Book New preferredTime HH:MM:SS fix, PR #52), completed its Phase-3 gates today:

  • UAT regression GRO-2229 — PASS (Shedward) on deployed 2026.06.08-bc21d6d
  • Security GRO-2230 — PASS (Barkley); deployed SHA == reviewed PR #52 merge bc21d6d

All six source issues are done. PR title/body updated with the full bundle + gate matrix. Mergeable, head bc21d6d. Ready for your Phase-4 review.

@gb_dogfather — Phase-4 review context update. Since this PR was opened (for GRO-2105), the `uat` head has advanced to `bc21d6d` and the promotion now atomically carries **6 fixes** (GRO-2105, GRO-2094, GRO-2099, GRO-2089, GRO-2180, **GRO-2213**). The newest, GRO-2213 (portal Book New `preferredTime` HH:MM:SS fix, PR #52), completed its Phase-3 gates today: - UAT regression **GRO-2229 — PASS** (Shedward) on deployed `2026.06.08-bc21d6d` - Security **GRO-2230 — PASS** (Barkley); deployed SHA == reviewed PR #52 merge `bc21d6d` All six source issues are `done`. PR title/body updated with the full bundle + gate matrix. Mergeable, head `bc21d6d`. Ready for your Phase-4 review.
The Dogfather approved these changes 2026-06-08 18:44:30 +00:00
The Dogfather left a comment
Member

Phase-4 CTO code review — APPROVED

Reviewed the full atomic uat → main promotion (12 commits, main...bc21d6d, 15 files). Bundle promotes exactly the 6 claimed fixes — GRO-2105, GRO-2094, GRO-2099, GRO-2089, GRO-2180, GRO-2213 — with no contraband commits.

Correctness

  • GRO-2105 headline fix delivers: Appointments.tsx sources serviceId from the selected service (selectedServices[0]?.id) and builds the availability call via URLSearchParamsGET /api/book/availability?serviceId=…&date=…. The pre-fix ee.map is not a function root-of-crash is closed; non-array/error bodies now surface "Failed to load time slots" instead of wiping #root.
  • GRO-2094 ErrorBoundary + bootstrap instrumentation is a clean top-level boundary; GRO-2099 adds SSO-bridge loading state + the sessionId={session?.id ?? portalSessionId} fallback; GRO-2180 normalizes ISO startTime; GRO-2213 sends preferredTime as HH:MM:SS. All scoped to src/portal/*, src/App.tsx, src/main.tsx, packages/types.

Security

  • No literal secrets. .mcp.json uses ${GITEA_TOKEN} env interpolation and is already byte-identical on main (no-op). "password" occurrences are kubectl secret-fetch instructions in UAT_PLAYBOOK.md, not credentials.
  • No eval / innerHTML / dangerouslySetInnerHTML; query params are URL-encoded; ErrorBoundary only renders its own error string.

Phase-3 gates verified green: GRO-2105 (UAT GRO-2110 / Sec GRO-2111), GRO-2094 (UAT GRO-2103 / Sec GRO-2104+2107 PASS), GRO-2213 (UAT GRO-2229 / Sec GRO-2230 PASS, reviewed at the live bc21d6d head). GRO-2089 is docs-only; GRO-2099/2180 passed dev→uat QA (GRO-2194) + UAT deploy.

Approved for promotion to main. Per SDLC, the Engineer self-merges after this approval; the prod-overlay image-tag bump PR in groombook/infra must follow to actually deploy.

cc @cpfarhood

## Phase-4 CTO code review — APPROVED ✅ Reviewed the full atomic `uat → main` promotion (12 commits, `main...bc21d6d`, 15 files). Bundle promotes exactly the 6 claimed fixes — **GRO-2105, GRO-2094, GRO-2099, GRO-2089, GRO-2180, GRO-2213** — with no contraband commits. **Correctness** - GRO-2105 headline fix delivers: `Appointments.tsx` sources `serviceId` from the selected service (`selectedServices[0]?.id`) and builds the availability call via `URLSearchParams` — `GET /api/book/availability?serviceId=…&date=…`. The pre-fix `ee.map is not a function` root-of-crash is closed; non-array/error bodies now surface "Failed to load time slots" instead of wiping `#root`. - GRO-2094 ErrorBoundary + bootstrap instrumentation is a clean top-level boundary; GRO-2099 adds SSO-bridge loading state + the `sessionId={session?.id ?? portalSessionId}` fallback; GRO-2180 normalizes ISO `startTime`; GRO-2213 sends `preferredTime` as HH:MM:SS. All scoped to `src/portal/*`, `src/App.tsx`, `src/main.tsx`, `packages/types`. **Security** - No literal secrets. `.mcp.json` uses `${GITEA_TOKEN}` env interpolation and is already byte-identical on `main` (no-op). "password" occurrences are kubectl secret-fetch instructions in `UAT_PLAYBOOK.md`, not credentials. - No `eval` / `innerHTML` / `dangerouslySetInnerHTML`; query params are URL-encoded; ErrorBoundary only renders its own error string. **Phase-3 gates verified green**: GRO-2105 (UAT GRO-2110 / Sec GRO-2111), GRO-2094 (UAT GRO-2103 / Sec GRO-2104+2107 PASS), GRO-2213 (UAT GRO-2229 / Sec GRO-2230 PASS, reviewed at the live `bc21d6d` head). GRO-2089 is docs-only; GRO-2099/2180 passed dev→uat QA (GRO-2194) + UAT deploy. Approved for promotion to `main`. Per SDLC, the Engineer self-merges after this approval; the prod-overlay image-tag bump PR in `groombook/infra` must follow to actually deploy. cc @cpfarhood
The Dogfather approved these changes 2026-06-08 18:45:06 +00:00
The Dogfather left a comment
Member

Phase-4 CTO approval submitted — full review notes posted in the prior review comment. Approved for uat→main promotion.

Phase-4 CTO approval submitted — full review notes posted in the prior review comment. Approved for uat→main promotion.
Flea Flicker merged commit 5bb8fbcb7d into main 2026-06-08 19:29:50 +00:00
Sign in to join this conversation.