- Layout: mobile hamburger + drawer nav (backdrop button + sibling nav),
desktop sidebar hidden on small screens, p-4 md:p-8 main padding
- Products: card list view on mobile, flex-wrap filters
- Lab results: card list view on mobile
- ProductForm: responsive grids (grid-cols-1 sm:grid-cols-2), skin
profile checkboxes 2→3 cols, active ingredient row restructured
(name+✕ in flex row, percent/strength/irritation in 3-col grid),
section headers stack on mobile
- Skin snapshots: date+icons on one row, badges on separate row below
- Product [id] header: back link stacked above title, redundant badge removed
- Routines header: flex-col on mobile, sm:flex-row
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Install @inlang/paraglide-js v2 with Vite plugin and paraglideMiddleware hook
- Add messages/pl.json and messages/en.json with ~400 translation keys
- Create project.inlang/settings.json (PL as base locale)
- Add LanguageSwitcher component (cookie-based, no URL prefix needed)
- Replace all hardcoded strings across 14 pages/components with m.*() calls
- ProductForm uses derived label maps for all enum types (category, texture, etc.)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add POST /products/parse-text endpoint that accepts raw product text,
calls Gemini (google-genai) with a structured extraction prompt, and
returns a partial ProductParseResponse. Frontend gains a collapsible
"AI pre-fill" card at the top of ProductForm that merges the LLM
response into all form fields reactively.
- Backend: ProductParseRequest/Response schemas, system prompt with
enum constraints, temperature=0.0 for deterministic extraction,
effect_profile always returned in full
- Frontend: parseProductText() in api.ts; controlled $state bindings
for all text/number/checkbox inputs; applyAiResult() merges response
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap all prop-derived $state initializers with untrack() to explicitly
signal that one-time capture from the product prop is intentional.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds full_weight_g and empty_weight_g to ProductBase (inherited by Product
and response models) so per-product package weight specs are captured.
Adds last_weighed_at to ProductInventory to record when a package was last
weighed. Wires up all fields through API schemas, frontend types, forms, and
the product detail page (add/edit/display).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Classification: replace fragmented layout (full-width + 3-col + full-width)
with a single 2-column grid; Category spans full width via col-span-2
- Effect profile: replace flex + fixed w-40 label with CSS grid using
minmax(7rem,10rem) label column to prevent overflow at narrow viewports
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract shared ProductForm.svelte component covering actives (dynamic
rows with ingredient function checkboxes), product_effect_profile
(range sliders 0–5), context_rules (tristate selects), synergizes_with,
incompatible_with (dynamic rows), and contraindications.
Replace the Quick edit stub on the product detail page with the full
edit form pre-populated from the existing product. Update both server
actions to parse all new fields including JSON payloads for complex
nested objects.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>