Adds the production evidence for commit 4281567 (`test:pwa` passing against the HTTPS
domain, the install button present with the chunk delayed, the chart running as a
CSSAnimation, smoke 16/16) and what this deploy taught about the setup:
- Auto-deploy webhooks are active on BOTH remotes, so pushing to gitea and github in
sequence queues two concurrent deploys of the same commit.
- `force=true` is never needed after a push; it stops the container before building and
adds avoidable downtime.
- Measured 241s of unavailability for this deploy — explicitly not attributed to cold
start, since a second deploy was building concurrently.
- This Coolify instance only answers on collection endpoints; everything per-resource
404s, so container env vars and logs are not readable over the API.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Neither is visible against localhost, which is why both shipped.
**Install button never appeared on `/login`** — a regression from making the login a
lazy route. `beforeinstallprompt` fires once per page load and is never replayed;
`useInstallPrompt` attached its listener from a `useEffect`, i.e. at mount, and
`InstallAppPrompt` now mounts only after an extra round-trip for its chunk. Locally
that round-trip is a millisecond so the listener still won a race it should never
have been in. Over a real connection the event was long gone, so a user on a slow
link lost the install button entirely.
The listener now lives in `src/lib/installPrompt.ts` and registers when the module
evaluates — `main.tsx` imports it for its side effect before mounting React. The hook
only reads from that store. Reproduced deterministically by delaying
`/assets/LoginPage-*.js` by 1.5s via `route()`: absent before, present after (also at
a 4s delay). `test:pwa` against the HTTPS domain now passes.
**Revenue chart did not animate on a real iPhone.** Measured rather than guessed:
the path animation does run in WebKit, and it triggers with the chart 100% visible at
y=601..715 of an 844px viewport — so neither "broken" nor "fires too early". What
fits is that iOS Safari suspends `requestAnimationFrame` during momentum scrolling
while framer-motion interpolates against wall-clock time: the animation spends its
1.4s without painting a frame and snaps to the end on resume, which looks exactly
like it never ran.
The chart's three animations move to CSS keyframes, which keep their own timeline in
the engine. The component only decides *when* (a `useInView` setting
`data-ld-rev-visible`). `prefers-reduced-motion` resolves in CSS too, and still
resolves to the *drawn* state — a line left at `dashoffset: 1px` with no animation
would be invisible forever. Verified: `getAnimations()` returns a `CSSAnimation`, and
under reduced motion the line renders complete.
Not verified: no physical iPhone here, and Playwright WebKit on Windows does not
reproduce iOS's rAF suspension. This is the standard mitigation and changes nothing
on desktop, but on-device confirmation is still outstanding.
Verified: typecheck clean; landing 13/13, PWA passed, responsive 0 findings, visual
62 screens / 0 errors, unit 39/39, e2e 33/33, admin 17/17, booking 12/12.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
README: route table for `/`, `/login` and `/b/:slug`; what `VITE_DEMO_UI` does to a
production build and how to turn the demo accounts off; the two test commands that
were missing (`test:landing`, `audit:responsive`).
progress.md: the deploy entry, including the defect it caught — the deployed bundle
had every magic-login string eliminated, so the new landing would have promised "no
registration" and led to an empty form — and the two measurement traps that cost a
false pass (a Playwright context without `hasTouch` reports `pointer: fine`, and
`GET /deployments/{uuid}` nests the application object before the deployment status).
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Adds a public funnel landing at `/` and moves the login to `/login`, rebuilt
around the panel's own colors instead of an invented palette.
Landing (`src/components/landing/`, one section per file, no props):
- Seven funnel sections composed by `LandingPage`. The hero's eight swatches are
literally `PIE_COLORS` from `DashboardPage`, the same hex values the seed hands
to avatars and services, so "your colors become your numbers" is literal.
- `BrandMark` becomes the single source for the logo, replicating
`public/favicon.svg`. Blue is the action surface, orange only ever marks.
- `NotebookVisual` is the one deliberate exception to the palette: it is what the
product replaces.
- Copy drops all system vocabulary; motion comes from `src/lib/motion.ts` with a
single easing, and reduced-motion resolves `initial` to the final state so a
never-firing `whileInView` cannot leave a section invisible forever.
Routing and bundle:
- `homePathFor` is the single definition of each role's destination.
- Landing, login, dashboard and calendar load lazily. Eager, the login dragged
framer-motion (~40 KB gz) into every panel load and the landing downloaded
recharts + FullCalendar (~187 KB gz) without charting anything. `clsx` is
pinned to the `react` chunk because Rollup otherwise assigns it to `charts`,
making the entry import 111 KB gz for a 200-byte utility.
Responsiveness (iPhone/iPad), verified with `npm run audit:responsive`:
- No touch form field below 16px, `dvh` height utilities, safe-area insets, and
40px touch targets keyed off `pointer: coarse` rather than `sm:`.
- New `.ld-gutter`: `.safe-x` lives outside `@layer` and beats Tailwind's `px-*`,
so it left the login's side padding at 0 on anything but an iPhone in
landscape. No overflow check could see it — there was no overflow, just zero
margin. The audit now guards it with a `gutter` check.
- `shell-height` no longer fires on pages that legitimately scroll; the static
`raw-viewport-unit` scan covers those instead.
Production build:
- The Dockerfile now sets `VITE_DEMO_UI=1` as a build arg. `DEMO` is a build-time
constant, so without it Vite eliminated the magic login and the "Ver como…"
switcher: the deployed landing promised "no registration" and led to an empty
form. Verified by building both ways and diffing the bundle.
- Service worker cache bumped to v2 so the orphaned pre-landing chunks get purged
from returning visitors' caches.
Verified: typecheck clean; unit 39/39, e2e 33/33, admin 17/17, booking 12/12,
landing 13/13 (WebKit), PWA passed against the real production build.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
- Rebrand all user-facing text to AgendaMax
- Fix sidebar scrolling: card container no longer overflows html (flex h-full min-h-0)
- Fix calendar toolbar hover: active buttons keep readable contrast
- Sticky calendar toolbar (month/week/day always visible on scroll)
- Seed guarantees 2-4 appointments per day for current week (Mon-Sat)