Skip to content

feat(maintenance): customer-facing scheduled-maintenance banner + modal#221

Merged
mastermanas805 merged 2 commits into
mainfrom
feat/maintenance-banner
Jun 11, 2026
Merged

feat(maintenance): customer-facing scheduled-maintenance banner + modal#221
mastermanas805 merged 2 commits into
mainfrom
feat/maintenance-banner

Conversation

@mastermanas805

Copy link
Copy Markdown
Member

What

A customer-facing scheduled-maintenance notice for the paused-cluster window. The prod cluster (api.instanode.dev) is intentionally paused, but the SPA at instanode.dev still loads from GitHub Pages — so visitors see the page render while every API call fails, which reads as a broken product. This makes the downtime read as intentional and reassures customers their data is safe.

Two surfaces, both styled with the existing design tokens (amber "warning" family + auth-card / modal shapes — not a raw browser alert):

  1. Sticky top banner on every route (marketing + app + login). Persists even after the modal is dismissed.
  2. One-time dismissible modal on /app* + /login* (where a customer hits confusing API errors first), with a clear ⚠️ icon. Dismissal is remembered for the tab session (sessionStorage); the banner stays.

Copy is calm + honest — "scheduled maintenance", "temporarily unavailable", "your data is safe", "back shortly". No mention of budget/cost/internal reasons.

Flag-gated: VITE_MAINTENANCE_MODE

  • Component reads import.meta.env.VITE_MAINTENANCE_MODE === '1'. Unset/'0' → renders null, zero markup (the default in CI, tests, local dev).
  • .github/workflows/deploy-pages.yml sets VITE_MAINTENANCE_MODE: '1' on the publish build step only (push to main / dispatch). PR builds + the separate build-and-test / playwright / coverage / lighthouse jobs never set it → they build with the notice OFF, so existing tests are unaffected.
  • Rendered into the prerendered HTML via entry-server.tsx, so the banner copy is present on the first byte of every static page (curl | grep maintenance will match after deploy).

Files

  • src/components/MaintenanceNotice.tsx — the component (banner + modal, flag-gated).
  • src/components/MaintenanceNotice.test.tsx — 18 tests (flag on/off, per-route modal, dismiss via button/overlay/Escape, session persistence, sessionStorage fail-open). 100% line/stmt coverage on the new file.
  • src/App.tsx — mounts <MaintenanceNotice /> inside BrowserRouter, above AppRoutes.
  • src/entry-server.tsx — renders it in the SSR/prerender path (public routes → banner in static HTML).
  • src/vite-env.d.ts — types VITE_MAINTENANCE_MODE.
  • .github/workflows/deploy-pages.yml — flag wiring.

CI expectations

Required checks (all should be green — none need the live API): build, build-and-test, coverage, playwright, knip, typos, links, up-to-date-with-base, scan / osv-scan.

The prod-dependent checks — e2e-pr-smoke, auth-contract-e2e, e2e-live, UI real-backend smokeWILL go red because the API is paused. That is expected and not required, so they don't block merge. No attempt is made to make them pass.

▶ How to turn the banner OFF on resume (operator)

Set VITE_MAINTENANCE_MODE back to '0' (or remove the line) in .github/workflows/deploy-pages.yml and re-run the deploy — or revert this PR. The component renders null when the flag is unset/'0', so the banner disappears on the next Pages publish (~1–2 min). No code change otherwise needed.

🤖 Generated with Claude Code

The prod cluster (api.instanode.dev) is intentionally paused for scheduled
maintenance, but the SPA still loads from GitHub Pages — so visitors see the
page render while every API call fails, which reads as a broken product.

Add a customer-facing notice that makes the downtime read as intentional:
- a sticky top banner on every route (marketing + app + login), and
- a one-time dismissible modal on /app* + /login* (where a customer would
  otherwise hit confusing API errors first), reinforced with a clear icon.

Copy is calm + honest ("scheduled maintenance", "temporarily unavailable",
"your data is safe", "back shortly") and styled with the existing design
tokens (amber warning family + auth-card/modal shapes) so it looks
intentional, not like a raw browser alert.

The whole thing is gated behind the build-time flag VITE_MAINTENANCE_MODE:
unset/'0' renders null (zero markup — the default in CI, tests, and local
dev); '1' renders the banner + modal. deploy-pages.yml sets it to '1' on the
publish build only (PR builds + the build-and-test/playwright/coverage/
lighthouse jobs never set it, so existing tests are unaffected). The notice
is rendered into the prerendered HTML via entry-server.tsx so the banner copy
is present on first byte of every static page.

To turn OFF on resume: set VITE_MAINTENANCE_MODE to '0' / remove it in
deploy-pages.yml and re-run the deploy (or revert this PR).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mastermanas805 mastermanas805 enabled auto-merge (squash) June 11, 2026 07:41
@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
dist/assets/index-oCnebW5x.js 0 B (-100% 🔽)
dist/assets/index-DhCc6Wh3.css 6.14 KB (0%)
dist/assets/index-GUEcPTYW.js 163.71 KB (+100% 🔺)

…gate

The required `playwright` job (ci.yml) runs the default playwright.config.ts
mocked suite with VITE_NO_PROXY=1 — every page.route() mock intercepts, so
nothing should touch the network. But the default testMatch glob
('**/*.spec.ts') also swept in e2e/auth-contract.spec.ts, which makes
UNCONDITIONAL real fetches to PROD (api.instanode.dev) to assert the AUTH-004
CORS envelope. That made the required gate silently depend on prod being
reachable: with the prod cluster paused, the fetch times out and the mocked
job fails for a reason unrelated to the PR (it was green before only because
prod happened to be up).

Add auth-contract.spec.ts to the default config's testIgnore. The spec keeps
full coverage via its dedicated playwright.auth-contract.config.ts +
auth-contract-e2e.yml workflow, which run it against prod (and are allowed to
go red while prod is down — they are not a required check). Mocked suite
verified locally: 55 passed / 1 skipped / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mastermanas805 mastermanas805 merged commit bc7778d into main Jun 11, 2026
17 of 20 checks passed
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.

1 participant