migration.sql was gitignored, so CI builds a Docker image from a
fresh clone with empty migration directories. prisma migrate deploy
then has nothing to apply, leaving the database schema-less on first
launch (P2021: table does not exist).
/app/data is a bind mount, so the chown baked into the image is
overwritten by the host directory's ownership at runtime. On a fresh
prod deploy Docker creates ./data as root-owned, but the container ran
as the non-root nextjs user, so Prisma couldn't open lovelope.db
("unable to open database file").
Add an entrypoint that starts as root, chowns /app/data, then drops to
nextjs via su-exec before running migrations and starting the server.
Spells out the two-channel CI trigger (dev branch for test builds, v*
tags for releases) as an actual step-by-step process: keeping dev in
sync with main/master, bumping package.json, tagging, and the
required Gitea repo secrets/variables.
Leftover naming from before the app was renamed to lovelope. Renamed
the local data/askedout.db file to data/lovelope.db to match (data/
is gitignored, so this doesn't touch version control).
Running the app no longer requires cloning the repo: pull the
published ghcr.io image directly via docker-compose. Move the
build-from-source flow (existing docker-compose.yml + Dockerfile) to
a secondary section for contributors.
Every push to master was triggering a build+push, since that's where
day-to-day commits land. Switch to two deliberate channels instead:
pushing to dev produces a rolling :dev tag for testing, and pushing a
v* tag produces :latest + the version tag for real releases. Plain
commits/merges to master/main no longer build anything on their own.
badge.tsx, dialog.tsx, and separator.tsx were never imported anywhere
in the app. Drop them along with @radix-ui/react-dialog and
@radix-ui/react-separator, which had no other consumers.
The Gitea instance's act_runner is the CI that actually executes here;
GitHub Actions on the mirror isn't confirmed to run at all (and a
push-mirror lacking the "workflow" PAT scope can silently drop
.github/workflows changes anyway). Add a GHCR login/push to the
existing .gitea/workflows/docker-publish.yml job instead, so one
build produces tags on both the Gitea registry and ghcr.io. Drop the
now-redundant .github/workflows/docker-publish.yml.
Requires a new repo secret GHCR_TOKEN: a GitHub PAT with write:packages
scope (separate from REGISTRY_TOKEN, which is scoped to the Gitea
registry).
.claude/skills/ui-styling was committed by accident in the initial
snapshot (93 files, ~5.7MB of font/skill assets unrelated to the app).
Untrack it and ignore /.claude going forward; files remain on disk.
Mirrors the existing .gitea/workflows/docker-publish.yml but targets
ghcr.io on the GitHub mirror. Simpler than the Gitea version since the
built-in GITHUB_TOKEN has native package-write rights on GHCR, no PAT
secret needed.
Restores the LICENSE content from Gitea's auto-generated initial
commit (overwritten by the force-push that unified history with the
pre-existing local project) and declares it in package.json.
github.repository preserves the owner's case (jeanGaston), but OCI
registries reject uppercase repository names. Compute a lowercased
IMAGE_NAME env var and use it for both tags instead.
The auto-injected GITHUB_TOKEN isn't authorized against this Gitea
instance's container registry (401 unauthorized), so use a repo
secret backed by a personal access token with package scope instead.
Gitea rejects variable/secret names starting with GITEA_ or GITHUB_
(reserved for system-injected values), which is why GITEA_REGISTRY
couldn't be created in repo settings.
act_runner's default registration only exposes ubuntu-latest (and
similar ubuntu-* labels); "docker" isn't a real label unless a runner
was explicitly configured with it.
The create-proposal submit had no error handling, so failures (e.g.
crypto.subtle unavailable outside a secure context) silently reset the
button with no feedback. Also set interactiveWidget so the mobile
keyboard resizes the layout instead of covering the sticky submit button.
Senders no longer need to fill in two activities to publish; one is
now enough (still capped at 5). Each activity can also specify a
place, which flows into the generated Google Calendar link and .ics
file when the recipient picks a slot.
The *.old.tsx files (page.old.tsx, layout.old.tsx, ManageClient.old.tsx,
etc.) were pre-refactor backups left alongside their replacements,
confirmed unreferenced by any import.
Also fixes a latent git-tracking bug: since the very first baseline
commit tracked these two files as Button.tsx/Card.tsx, and Windows'
case-insensitive filesystem means a same-path case change isn't
recorded as a rename by default, git's tree kept the capitalized path
forever even after they were rewritten to shadcn's lowercase
button.tsx/card.tsx content. This surfaced intermittently as a
TS1261 "differs only in casing" build error whenever git re-checked-out
the tree (e.g. after this branch's merge to master rewrote the working
copy back to the capitalized path). Fixed with a two-hop git mv so the
rename is actually recorded.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
easeInOut was applied per-segment across all 6 keyframe boundaries,
decelerating to zero velocity at each one - technically continuous
but visually reads as discrete steps rather than one fluid motion.
Switched to linear; the non-uniform spacing already baked into the
`times` array (tight around the crossing, wide at the ends) now does
the speed-shaping instead, without ever fully stopping mid-flight.
Verified by sampling translateX every animation frame: previously
would have shown velocity flattening at each keyframe boundary, now
shows a steady ~1px/16ms monotonic decrease start to finish.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous commit only masked the emoji row's crossing (via the
white flash timed to their fade). The text row ("love"/"envelope")
still animated opacity down while sitting fully centered on top of
each other, so the two words visibly superposed for a beat with no
flash to hide it there.
Fix applies the same technique to both rows: x and opacity now reach
their end state (centered / invisible) together, so there's no frame
where a glyph is both fully opaque and fully overlapping another.
Verified via a fresh screenshot mid-transition - no double-exposure.
Also addressed reported animation lag: dropped the rotate wobble
(one fewer animated property per element), cut keyframe count 8->7,
shortened 2.4s -> 1.8s, trimmed the flash's blur/spread, and added
willChange hints so all layers are promoted upfront rather than
mid-animation. Playwright rAF sampling over the full animation now
shows a flat 60fps with zero frames slower than 30fps.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LoveFusion:
- Swapped sides per request: love (was heart) now enters from the
left, envelope from the right, converging at center.
- Retimed the crossfade so opacity/scale start dropping the instant
they reach center instead of dwelling there fully-opaque and
overlapping - the previous timing had a visible window where the
heart span rendered on top of the envelope span (DOM order stacking)
before either had faded, which read as a glitch rather than a merge.
- The white flash now ramps up in sync with that fade (bigger too:
w-8 base, larger blur/spread, peak scale 2.4 vs 1.8) so it's already
bright enough to mask the crossing before the two glyphs are fully
gone, then blooms back out into the merged emoji.
- Wordmark text ("love"/"envelope"/"lovelope.app") now renders in
Hachi Maru Pop (next/font/google), scoped to this component only -
the app's Bricolage Grotesque display font elsewhere is untouched.
Favicon:
- Replaced the envelope-outline SVG mark with 💌 on the same pink
rounded-square backdrop, moved from public/icon.svg to
src/app/icon.svg (Next's file-based icon convention, so it's
auto-linked as <link rel="icon">). Tried next/og's ImageResponse
first but it hit a Windows-specific "Invalid URL" bug in the
Next 14.2.35-bundled @vercel/og font loader; a static SVG sidesteps
it entirely and is simpler besides. manifest.webmanifest already
pointed at /icon.svg so no change needed there - same URL, new
source file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Pulled from the ui-styling skill's canvas-fonts pack (both OFL-licensed,
license files kept alongside in src/fonts/) instead of next/font/google,
per explicit choice to use the skill's bundled assets rather than a
Google Fonts pairing.
- Display (headings): Bricolage Grotesque (Regular + Bold) - warmer,
more distinctive than Poppins while keeping the same bold/rounded
energy for the "adorable" branding.
- Body: Instrument Sans (Regular/Italic/Bold/BoldItalic) - clean at
form-field sizes.
- CSS variables renamed --font-inter/--font-poppins -> --font-sans/
--font-display (decoupled from the specific font choice); updated
in tailwind.config.ts and layout.tsx. Removed the redundant static
fallback declarations in globals.css - next/font's `variable`
option already injects these via the <html> className.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Swaps min-h-screen -> min-h-[100dvh] across all five screen states
(expired, key-missing, loading, answered, reveal) so mobile browser
chrome (address bar) doesn't clip full-bleed gradient screens.
Bottom padding on the main content column now respects
env(safe-area-inset-bottom) for iOS home-indicator devices.
This page intentionally keeps its own theme.* driven styling
(cardBg/cardBorder/textPrimary/buttonGradient from lib/themes.ts)
rather than shadcn Card/Button — those tokens ARE the per-proposal
color set this refactor was told to preserve exactly, so wrapping
them in the shadcn primitives would fight the dynamic theming
instead of composing with it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ProposalForm:
- Step tabs -> Radix Tabs (roving tabindex, arrow-key nav, proper
ARIA) driven by the same `step` state as before.
- Evasive-no toggle -> Switch, wrapped in a <label> so the whole row
(not just the small control) is tappable.
- All raw <input>/<textarea> -> Input/Textarea; buttonVariants
'primary' -> 'default' to match the new variant names.
- New StepActions wrapper makes Back/Next/Publish sticky to the
viewport bottom on mobile (safe-area aware) so the wizard never
requires scrolling to find the next action; reverts to normal
inline flow at sm+.
CreateClient / ManageClient:
- Card/Button/Input swapped to shadcn equivalents.
- Copy-link feedback moved from a per-button `copied` boolean +
setTimeout to a single sonner toast call, removing the duplicated
state pattern.
- ManageClient's delete confirmation moved from window.confirm() to
an AlertDialog, matching the skill's accessible-primitives guidance
and giving it proper focus trapping on mobile.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Field.tsx re-added composing shadcn Label (no built-in equivalent
in this CLI generation).
- Bumped Input/Textarea to h-12/rounded-xl/px-4 (44px+ touch targets,
matches the app's existing rounded, roomy input style).
- Card padding restored to the old flat p-5 sm:p-6 default so most
call sites (`<Card>children</Card>`) don't need CardContent
wrapping; CardHeader/Content/Footer still exported for the few
structured cards.
- Swapped literal pink-500/gray-100 focus/border colors for
text-primary / border-border / ring-ring tokens in Footer and
CalendarButtons so brand color flows from the CSS vars.
- min-h-screen -> min-h-[100dvh] on full-bleed screens (home,
create, not-found) so mobile browser chrome doesn't clip content.
- Responsive type scale (text-4xl sm:text-5xl md:text-6xl, etc.)
instead of a single jump at md:, so headings don't overflow on
narrow phones.
- Root layout now mounts <Toaster> and uses bg-background/
text-foreground (background token matches the previous gray-50).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Pinned the CLI to shadcn@2.3.0 rather than the latest (4.16.0): the
latest generation targets Tailwind v4-only syntax (bare-value parens
like `gap-(--card-spacing)`, the `in-*` variant, `--spacing()`) that
silently fails to compile under this project's Tailwind v3.4.6.
2.3.0 is the last CLI generation producing classic hsl(var(--x))
v3-compatible component output.
Swapped @radix-ui/react-icons for the already-installed lucide-react
in dialog.tsx, and dropped sonner's next-themes dependency (the app
has no dark-mode toggle, so Toaster is hardcoded to theme="light").
Old custom src/components/ui/Button.tsx, Card.tsx, and Field.tsx are
removed here; call sites are updated in the following commits.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds components.json, cn() util, Radix + cva/clsx/tailwind-merge/
lucide-react/sonner deps, and CSS-variable color tokens mapped onto
the brand's existing pink-500 primary (#ec4899) and neutral grays.
`shadcn init` defaulted to Tailwind v4-style output (oklch colors,
`@theme`-driven CSS, `tw-animate-css`) without updating
tailwind.config.ts, which would not have compiled against the
project's pinned Tailwind v3. Hand-wrote the classic v3 integration
instead (HSL vars + tailwind.config.ts color/radius mapping +
tailwindcss-animate) rather than pull in an unplanned Tailwind
major-version migration.
Also drops the sunset/neon/pastel raw color scales from
tailwind.config.ts: grep confirmed no class ever referenced them,
they were leftover from before lib/themes.ts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Captures the existing lovelope.app codebase (Next.js App Router,
custom Tailwind components, Prisma) as a rollback point prior to
adopting shadcn/ui and a mobile-first redesign.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>