Hardcoded secure: true silently drops the cookie on HTTP (localhost),
causing the post-join redirect to bounce back to /login. Use $app/environment
dev flag so the cookie works in development while remaining Secure in production.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove duplicated private authenticateInSession from AuthController and
HouseholdController. Add a single public implementation on AuthService
with session fixation protection built in. HouseholdController now
injects AuthService and passes role "user" for invite-accepted accounts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same invalidatedAt gap as getInviteInfo: a superseded invite (status
still 'pending', invalidatedAt set) could still be used to create an
account and join the household.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Superseded invites had invalidatedAt set but status stayed 'pending',
so they passed the validity check and could still be viewed and accepted.
Add invalidatedAt != null guard to getInviteInfo.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- createInvite: use saveAndFlush when invalidating existing invite so the
UPDATE is guaranteed to hit the DB before the new INSERT, preventing
duplicate key violation on uq_household_invite_active
- acceptInvite: also set invalidated_at when marking invite as used, so
accepted invites are fully removed from the partial unique index and
cannot block future invite creation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SecurityConfig: /** covers /v1/invites/{code}/accept (two path segments);
/* only matched one segment so the accept endpoint was returning 401
- HouseholdIdentityPanel + page: use --green-dark bg (matching BrandPanel
on login) instead of --green-tint; text updated to white/--green-light
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dev databases that accumulated multiple pending invites before V026 was
written would fail to create uq_household_invite_active. Added a cleanup
UPDATE that marks all-but-the-latest invite per household as invalidated
before the index is created.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- handleInviteClick: show toast and bail early when POST /invites fails
- handleRegenerate: show toast when regeneration POST fails
- handleRoleChange: add Content-Type: application/json header on PATCH
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
removeMember now checks the planner count before deleting a planner
member. Throws ConflictException("Cannot remove the last planner")
when only one planner remains, matching the spec requirement in S4.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces hardcoded \"https://yourapp.com\" with a Spring property.
- application.yml: app.base-url defaults to http://localhost:5173
- application-docker.yml: reads APP_BASE_URL env var, same default
- HouseholdService: injects @Value("${app.base-url}") and uses it in
toInviteResponse() to build shareUrl
- HouseholdServiceTest: sets field via ReflectionTestUtils in @BeforeEach;
adds test asserting shareUrl starts with configured base URL
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
POST /members/invites was returning the full ApiResponseInviteResponse
wrapper. The client set activeInvite directly from the response body,
so shareUrl/inviteCode/expiresAt were missing (nested under .data).
Fixed to return data?.data — the inner InviteResponse — matching the
shape that InvitePanel and page.server.ts already expect.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When activeInvite is null and the user clicks the invite card, POST to
/members/invites first to generate a code, then toggle the panel open.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MemberCard: white bg, 1px border + shadow-card, centered column layout,
avatar color by role (green-dark/blue), role badge with role-specific colors,
join date "seit DD.MM.YYYY", Du-badge below join date, ⋯ kebab with icons
and divider, inline role-control with Abbrechen, blue editing border #B5D4F4
- InviteCard: white bg, 1.5px dashed border, min-height 180px, plus circle,
label "Mitglied einladen", full hover state (green border/bg/icon/label)
- InvitePanel: white bg, title "Einladelink teilen", description, mono link
box, yellow expiry pill when ≤ 24h, text-link "Neuen Link generieren"
- RemoveDialog: white bg, padding 28px 32px, "?" in title, updated body text
- +page.server.ts: expose householdName from locals.haushalt
- +page.svelte: subtitle "{n} Mitglieder · {householdName}"
- Tests: add join date format test, Abbrechen test, InvitePanel title test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add --color-border-hover to the design system neutrals and replace the
hardcoded hex in all three card definitions (settings hub ×2, SettingsCard).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The green left border was removed from the design — the accent prop,
data-accent attribute, and inline style were never used on the hub page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The outer {:else} already guarantees isOnboarding is false — inner guards
were always-true dead conditions unreachable by tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds beforeEach(vi.clearAllMocks) to prevent shared vi.fn() state in
baseProps from leaking across tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds regression test for the {#if slot.id} guard on the remove button —
QA flagged the missing negative test case for optimistic slots.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- border-radius: 10px → var(--radius-lg) in both tile components
- opacity: 0.42 → var(--opacity-dimmed) in DesktopDayTile
- var(--yellow) → var(--color-ring-today) for today ring and date circle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces (t: any) with (t: TagItem) so the API response shape is
validated against the shared TagItem interface.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extracts sanitizeForCssUrl helper that strips '"()\ before the URL
is embedded in url("..."). Prevents CSS injection via the hero image
field in inline style bindings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes local TagItem, Recipe, SlotRecipe, Slot, SlotMap definitions
and imports Recipe, Slot, SlotMap from types.ts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes local TagItem, SuggestionRecipe, TopSuggestion, Slot interfaces
and imports Suggestion, SlotMap from types.ts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes local TagItem, SlotRecipe, Slot, Suggestion interfaces and
imports Recipe, Slot, Suggestion from types.ts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds shared Slot and SlotMap interfaces so DesktopDayTile,
EmptyDayTile, and reasoningTags can import rather than re-declare.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
transform-style:preserve-3d on a parent with box-shadow/transition
causes Chrome to fail backface-visibility:hidden. Replace with
independent per-face rotateY transforms:
front: 0deg → -180deg (flipped)
back: 180deg → 0deg (flipped)
No preserve-3d needed — each face is its own compositing layer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
overflow:hidden on direct children of preserve-3d flattens the 3D
context in Chrome, causing backface-visibility:hidden to fail.
Move border-radius + overflow to inner wrapper divs (.card-front-inner,
.card-back-inner) and keep the face elements themselves free of those
properties. Also add -webkit-backface-visibility:hidden and
will-change:transform for consistent GPU compositing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- backface-visibility hides elements visually but not to pointer events;
disable pointer events on the hidden face explicitly so the X button
on the back face is clickable and the front face doesn't intercept clicks
- Add .scene-selected:hover rule so green ring is not overwritten by the
higher-specificity .scene:hover box-shadow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>