diff --git a/CLAUDE.md b/CLAUDE.md index 63ec452..47c2db6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -93,6 +93,7 @@ infra/ ```bash just setup # first-time (and idempotent) local environment setup just dev # start docker-compose.dev.yml + SvelteKit dev server +just serve # build + start prod Node server at :3000 against dev Docker stack just stop # stop all services (Docker stack + Vite dev server) just dev-stop # stop Docker stack only just dev-clean # stop Docker stack and remove all volumes (full reset) @@ -173,7 +174,9 @@ Logout is a single call — `supabase.auth.signOut()` — which clears the GoTru 7. **`onAuthStateChange` fires `INITIAL_SESSION` on page load, not `SIGNED_IN`.** `SIGNED_IN` only fires immediately after a login flow. Load collective data whenever the session is present, regardless of event type — otherwise collectives are never loaded on page refresh. Pattern in root `+layout.svelte`: `if (session) { await loadUserCollectives(...) }` covering `INITIAL_SESSION`, `SIGNED_IN`, and `TOKEN_REFRESHED`. -8. **PWA service worker: do not use `injectManifest` with a file named `service-worker.ts`.** SvelteKit intercepts any file at `src/service-worker.[jt]s` and enforces a hard restriction: only `$service-worker` and `$env/static/public` may be imported — Workbox modules are blocked. With `injectManifest`, `@vite-pwa/sveltekit` tries to find the compiled SW output at `.svelte-kit/output/client/service-worker.js`, but SvelteKit never produces it (compilation fails on the blocked imports). Fix for Fase 1–2a: use `generateSW` strategy (plugin auto-generates the SW, no custom source needed). Fix for Fase 2b when a custom SW is needed: name the file `src/sw.ts` — SvelteKit does not recognise it as a service worker — and set `strategies: 'injectManifest'`, `filename: 'sw.ts'` in `vite.config.ts`. +9. **CSS custom properties use RGB triplets — always use `rgb()`, never `hsl()`.** All design tokens in `app.css` store values as space-separated RGB triplets (e.g. `51 65 85`). Using `hsl(var(--token))` interprets the first value as a hue degree — `hsl(51 65% 85%)` renders as light yellow, not slate-700. The Tailwind config and any `background-color`/`color` declarations in `app.css` must use `rgb(var(--token) / )`. + +10. **PWA service worker: do not use `injectManifest` with a file named `service-worker.ts`.** SvelteKit intercepts any file at `src/service-worker.[jt]s` and enforces a hard restriction: only `$service-worker` and `$env/static/public` may be imported — Workbox modules are blocked. With `injectManifest`, `@vite-pwa/sveltekit` tries to find the compiled SW output at `.svelte-kit/output/client/service-worker.js`, but SvelteKit never produces it (compilation fails on the blocked imports). Fix for Fase 1–2a: use `generateSW` strategy (plugin auto-generates the SW, no custom source needed). Fix for Fase 2b when a custom SW is needed: name the file `src/sw.ts` — SvelteKit does not recognise it as a service worker — and set `strategies: 'injectManifest'`, `filename: 'sw.ts'` in `vite.config.ts`. ## Sync Strategy diff --git a/DESIGN.md b/DESIGN.md index 4283b55..73f8361 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,442 +1,96 @@ -# DESIGN.md - -Design reference for the Colectivo app. Stack: SvelteKit + shadcn-svelte + Tailwind CSS. - -Generated mockups are in `.stitch/designs/`. Stitch project ID: `10316144241804324155`. - +# Design System Specification + +## 1. Overview & Creative North Star: "The Monolith Editorial" +The creative direction for this design system is **The Monolith Editorial**. We are moving away from the "busy" aesthetics of modern SaaS and toward a disciplined, high-end editorial experience. The goal is to create a sense of permanent, quiet authority. By stripping away all accent colors and relying on a monochromatic slate palette, we force the user to focus on content, structure, and intent. + +The "Template" look is avoided through **Intentional Negative Space** and **Tonal Depth**. We do not use lines to separate ideas; we use the weight of the typography and the shift of the background. It is a system that feels carved rather than assembled. + --- - -## Visual Style - -### Direction - -Clean and minimal. Content is the UI — no decorative elements, no gradients, no shadows unless they carry meaning. Generous whitespace. Every element earns its place. - -### Color - -Monochromatic slate palette. No accent color — contrast and weight carry hierarchy. - -``` ---background: slate-50 / slate-950 ---surface: white / slate-900 ---surface-raised: slate-100 / slate-800 ---border: slate-200 / slate-700 ---text-primary: slate-900 / slate-50 ---text-secondary: slate-500 / slate-400 ---text-disabled: slate-300 / slate-600 ---destructive: red-500 / red-400 ← only color in the UI -``` - -Interactive elements (buttons, checkboxes, active states) use `slate-900` on light and `slate-50` on dark — inverting the surface. The only true color is destructive red. - -### Typography - -Font: **Inter** (or `system-ui` as fallback — acceptable on iOS/macOS). - -| Role | Size | Weight | Notes | -|---|---|---|---| -| Screen title | 20px | 600 | | -| Section label | 13px | 600 | Uppercase, `letter-spacing: 0.05em` | -| Body / list item | 16px | 400 | | -| Meta / secondary | 13px | 400 | `text-slate-500` | -| Badge / tag | 11px | 500 | Uppercase, `rounded-full` | -| Input | 16px | 400 | Min 16px prevents iOS zoom on focus | - -Line height: 1.5 for body, 1.2 for headings. No text smaller than 11px. - -### Spacing - -Base unit: 4px (Tailwind default). Prefer multiples of 4. Common values: - -- List item padding: `px-4 py-3` (min 48px touch target) -- Screen horizontal margin: `px-4` (mobile), `px-6` (desktop) -- Section gap: `gap-6` -- Between section label and content: `mt-2` -- Bottom nav / sticky input safe area: always account for `env(safe-area-inset-bottom)` - -### Iconography - -**Lucide** (ships with shadcn-svelte). Size `20px` inline, `24px` for nav and primary actions. Stroke width `1.5`. Never fill icons. - -### Dark Mode - -Class-based (`dark` on ``). Respect `prefers-color-scheme` on first load, then persist user preference in `localStorage`. shadcn-svelte's CSS variable system handles theming — no additional setup needed per component. - + +## 2. Colors & Tonal Architecture +The palette is strictly monochromatic, utilizing a "Slate" foundation to provide a cooling, sophisticated atmosphere. + +### Core Palette +- **Background**: `#f7f9fb` (Light) / `#020617` (Dark) +- **Primary (Action)**: `#000000` (Light) / `#ffffff` (Dark) — *Note: Primary actions invert the surface.* +- **Destructive**: `#ba1a1a` (The only color allowed to break the monochrome seal). + +### The "No-Line" Rule +**Explicit Instruction:** Designers are prohibited from using 1px solid borders to section off major UI areas. To define boundaries, you must use background shifts. +- Use `surface-container-low` for secondary content areas. +- Use `surface-container-highest` for high-priority nested components. +- Sectioning is achieved via a 32px–48px vertical gap or a subtle change in the background hex, never a line. + +### Surface Hierarchy & Nesting +Treat the UI as stacked sheets of gallery-grade paper. +1. **Base Layer:** `surface` (The foundation). +2. **Raised Layer:** `surface-container-low` (For subtle grouping). +3. **Focus Layer:** `surface-container-lowest` (For cards and interactive modules to "pop" via tonal contrast). + --- - -## Component Library - -**shadcn-svelte** — copy-paste components built on bits-ui + Tailwind. No runtime dependency, full ownership of component code. - -### Conventions - -- Copy components into `apps/web/src/lib/components/ui/` (shadcn-svelte default) -- App-specific compositions go in `apps/web/src/lib/components/` (e.g. `ItemRow.svelte`, `ListCard.svelte`) -- Never modify shadcn-svelte primitives directly — compose or wrap them -- Use shadcn-svelte's `cn()` utility for conditional classes - -### Key components to install - -| Component | Used for | -|---|---| -| `Button` | All actions | -| `Input` | Add item, search, forms | -| `Checkbox` | Shopping items, tasks | -| `Sheet` | Mobile bottom drawers (item edit, member invite, avatar picker) | -| `Dialog` | Confirmations (delete list, dissolve collective) | -| `Popover` | Presence avatars tooltip | -| `Badge` | Priority tags (TODAY, URGENT), status labels (DRAFT, HIGH PRIORITY) | -| `Skeleton` | Loading states | -| `Sonner` | Toast notifications (sync errors, member joined, undo delete) | - + +## 3. Typography: The Inter Hierarchy +We use **Inter** exclusively. To achieve an editorial feel, we rely on extreme contrast between `headline-lg` and `label-sm`. + +| Level | Size | Weight | Case | Spacing | +| :--- | :--- | :--- | :--- | :--- | +| **Display-LG** | 3.5rem | 600 | Default | -0.02em | +| **Title-LG** | 20px | 600 | Default | -0.01em | +| **Header (Label-MD)** | 13px | 600 | Uppercase | +0.05em | +| **Body-LG** | 16px | 400 | Default | Normal | +| **Meta (Label-SM)** | 13px | 400 | Default | Normal | + +**Editorial Note:** Always pair an Uppercase Label-MD header directly above a Title-LG to create a "Masthead" effect for sections. + --- - -## Layout & Navigation - -### App Shell - -Two-column on desktop, single-column on mobile. - -``` -Mobile Desktop (lg+) -┌─────────────────────┐ ┌──────────┬──────────────────────┐ -│ Header │ │ │ Breadcrumb / Title │ -├─────────────────────┤ │ Sidebar │──────────────────────┤ -│ │ │ │ │ -│ Content │ │ │ Content │ -│ │ │ │ │ -├─────────────────────┤ │ User │ │ -│ Bottom Nav │ │ Info │ │ -└─────────────────────┘ └──────────┴──────────────────────┘ -``` - -### Sidebar (desktop) - -Fixed left sidebar, `surface-raised` background (vs `surface` main area — tonal shift, no border line). - -``` -┌──────────────┐ -│ Colectivo │ ← collective name, 14px/600 -├──────────────┤ -│ 🛒 Lists │ ← active item: slate-900 bg, white text -│ ✓ Tasks │ ← inactive: slate-500 text -│ 📄 Notes │ -│ 🔍 Search │ -│ │ -│ (spacer) │ -│ │ -│ ┌──┐ Name │ ← user avatar + display name -│ │AG│ Role │ ⚙ ← settings gear icon -└──────────────┘ -``` - -- Collective name at top, 14px/600 -- 4 nav items: Lists, Tasks, Notes, Search -- User avatar + display name + settings gear at bottom -- Width: `w-56` (`224px`) - -### Navigation (mobile) - -Fixed bottom tab bar, 4 tabs. Icons only — no text labels. Active icon: `slate-900`. Inactive: `slate-400`. Glassmorphism background: `backdrop-blur-md bg-white/80`. Respects `safe-area-inset-bottom`. - -| Tab | Icon | Route | -|---|---|---| -| Lists | `shopping-cart` | `/lists` | -| Tasks | `check-square` | `/tasks` | -| Notes | `file-text` | `/notes` | -| Search | `search` | `/search` | - -### Page Headers - -Desktop: breadcrumb-style context path above the title, in uppercase 11px/500 `text-slate-400`: - -``` -WORKSPACE OVERVIEW -Shared Lists -``` - -``` -PROJECT ATLAS / Tasks -``` - -Mobile: plain title with optional back arrow and overflow `⋯` menu on the right. - -### Responsive Breakpoints - -Tailwind defaults: `sm` (640), `md` (768), `lg` (1024). Sidebar appears at `lg`. Content max-width: `max-w-2xl` centered on large screens. - + +## 4. Elevation & Depth: Tonal Layering +Traditional drop shadows are largely forbidden. Hierarchy must be achieved through **Tonal Layering**. + +- **The Layering Principle:** Instead of a shadow, place a `surface-container-lowest` card on a `surface-container-low` background. The slight shift in slate value creates a "natural" lift. +- **Ambient Shadows:** For floating elements like Sheets or Dialogs, use a "Ghost Shadow": `0px 20px 40px rgba(15, 23, 42, 0.06)`. It should feel like a soft glow, not a dark stain. +- **Glassmorphism:** For the "Sticky Bottom Input," use a backdrop-blur of `12px` and a semi-transparent `surface` color (80% opacity). This ensures the content "flows" under the interface, maintaining a sense of depth. + --- - -## Screen Patterns - -### Lists View (`/lists`) - -Featured card for the primary/active list (full width, larger), secondary lists in a 2-column grid below. - -``` -WORKSPACE OVERVIEW -Shared Lists -┌─────────────────────────────────────┐ -│ Home 👤👤 │ ← featured card -│ 12 items · Updated 2h ago │ -│ [GROCERIES] [HOUSEHOLD] │ ← category tags, rounded-full -└─────────────────────────────────────┘ -┌──────────────┐ ┌──────────────┐ -│ Office │ │ Party │ -│ 5 items │ │ 26 items │ -│ 🗂 │ │ HIGH PRTY │ ← badge -└──────────────┘ └──────────────┘ -┌─────────────────────────────────────┐ -│ + Create a new shared list... [Create] │ ← sticky bottom input -└─────────────────────────────────────┘ -``` - -- Category tags on list cards: `rounded-full text-xs font-medium bg-slate-100 px-2 py-0.5` -- Presence avatars overlap on cards with active members - -### List Detail (`/lists/[id]`) - -Normal (non-session) view. Items with quantity controls. - -``` -ACTIVE LIST -Grocery List ⋯ -┌─────────────────────────────────────┐ -│ ☐ Honeycrisp Apples 6 + │ -│ ☐ Organic Baby Spinach 2 + │ -│ ☐ Whole milk 1L 2 + │ -│ ☐ Salted Butter 1 + │ -└─────────────────────────────────────┘ - - [MILK] [BREAD] [EGGS] [COFFEE] [APPLES] ← frequency chips -┌─────────────────────────────────────┐ -│ Add item (e.g. Greek Yogurt 500g) [+] │ -└─────────────────────────────────────┘ -``` - -- Quantity controls: `−` count `+` inline at the right of each row -- Frequency chips always visible above the input (not just on focus); active chip: `bg-slate-900 text-white`; inactive: `bg-slate-100 text-slate-700` - -### Shopping Session Mode (`/lists/[id]/session`) - -Full-screen takeover. No sidebar, no bottom nav. - -``` -✕ Compra semanal 👤👤 -───────────────────────────────────── -TO BUY - ☐ Whole milk 1L - ☐ Bread - ☐ Eggs 12 uds - ☐ Avocados - -CHECKED - ☑ Butter 250g (muted, struck through) - ☑ Olive Oil 1L - -───────────────────────────────────── -[ Finish shopping ● ] -``` - -- `TO BUY` and `CHECKED` are uppercase section labels, not divider lines -- Items slide from TO BUY to CHECKED on tap — no page scroll -- Minimum tap target: `min-h-[56px]` -- Presence avatars: `size-6 rounded-full`, overlapping, top-right of header -- "Finish shopping" button: full-width, `bg-slate-900 text-white`, with a subtle pulse indicator dot (●) - -### Tasks View (`/tasks`) - -Tasks grouped into named sections. Filter pills at the bottom. - -``` -PROJECT ATLAS / Tasks 👤👤 -───────────────────────────────────── -HIGH PRIORITY - ☐ Finalize Q4 strategy deck TODAY - ☐ Coordinate with design on monolith tokens URGENT - -PRODUCT DEVELOPMENT - ☐ Core API Integration - ☐ Review documentation for Auth endpoints (indented sub-item) - ☑ Map database schema to editorial structure (completed) - ☐ User testing: Editorial workflow - -BACKLOG - ☐ Update dependency libraries - -───────────────────────────────────── - [DAILY] [WEEKLY] [URGENT] ← filter pills -┌─────────────────────────────────────┐ -│ Add task to Tasks... [Save] │ -└─────────────────────────────────────┘ -``` - -- Section names are user-defined (not system-fixed) -- Priority badges on individual tasks: `text-xs uppercase text-slate-400` (TODAY, URGENT) -- Filter pills: same style as frequency chips; filter the visible tasks - -### Notes View (`/notes`) - -Notes displayed as cards in a masonry-style grid, grouped by user-defined categories. - -``` -WORKSPACE / NOTES 🔔 ⊞ 👤 -───────────────────────────────────── -CATEGORY -Meeting Notes -┌─────────────────┐ ┌─────────────────┐ -│ Q4 Product │ │ Sprint Retro: │ -│ Roadmap... │ │ Project Apollo │ -│ Oct 24, 2023 │ │ Oct 22, 2023 │ -└─────────────────┘ └─────────────────┘ - -CATEGORY -Project Ideas -┌─────────────────┐ -│ Minimalist │ -│ Portfolio... │ -└─────────────────┘ - -───────────────────────────────────── -│ Capture a new thought... # @ [SAVE] │ -``` - -- Cards: `bg-white rounded-md` on `surface-raised` background (tonal lift, no shadow) -- Category labels: uppercase section labels above each group -- Inline new-note input at the bottom (not a separate screen) - + +## 5. Components +All components follow a 4px base grid and a `rounded-md` (0.375rem) corner radius. + +### Buttons (Inversion Logic) +- **Primary:** `bg-slate-900 text-slate-50` (Light Mode). This "Inversion" is the signature interactive state. +- **Destructive:** `bg-red-500 text-white`. Reserved only for irreversible actions. +- **Ghost:** No background, `text-slate-500`. On hover, shift to `surface-raised`. + +### Input Fields & Sticky Input +- **The Monolith Input:** A sticky bottom container with `backdrop-blur`. No border; only a subtle `surface-raised` background. +- **Chips:** `rounded-full`, `bg-slate-200`. When active, they invert to `bg-slate-900 text-white`. + +### Lists & Swipe Actions +- **Forbid Dividers:** Do not use lines between list items. Use 12px of vertical padding (`py-3`) to create separation through whitespace. +- **Swipe-to-Delete:** The "Red Zone" emerges from behind the item. As the user swipes, the background transitions into `red-500` with a white Lucide `trash-2` icon. + +### Shopping Mode Components +- **Large Tap Targets:** Increase minimum hit area to 56px. +- **Presence Avatars:** 24px circles with a 2px `surface` border, overlapped horizontally to show collaboration. + --- - -## UX Patterns - -### Optimistic Updates - -All mutations update the local store immediately before the server responds. On error, revert and show a toast. Never block the UI waiting for a network response. - -### Offline Indicator - -A slim bar at the top of the content area (not the header) when offline: - -``` - No connection · Saving locally -``` - -`bg-slate-800 text-slate-100`, 36px tall. Mounts/unmounts immediately — no animation. - -### Frequency Chips (Add Item Input) - -Sticky input area at the bottom of list and task screens. Frequency chips are **always visible** above the input, not conditional on focus. - -``` - [MILK] [BREAD ●] [EGGS] [COFFEE] [APPLES] ← scrollable row -┌─────────────────────────────────────────────────┐ -│ Add item (e.g. Greek Yogurt 500g) [+] │ -└─────────────────────────────────────────────────┘ -``` - -- Chips: `rounded-full text-xs font-medium px-3 py-1 bg-slate-100` (inactive) / `bg-slate-900 text-white` (active/selected) -- Tapping a chip fills the input; user can still edit before confirming -- While typing, chips switch to prefix-filtered suggestions (max 10, same pill style) -- Confirm with `+` button or `Enter` - -### Swipe to Delete - -On list items and tasks. Swipe left reveals a red destructive zone: - -``` -┌────────────────────────────────────────┐ -│ ☐ Whole milk 1L ░░░░░░ │ ← item sliding left -│ [🗑] │ ← red-500 zone, trash icon only -└────────────────────────────────────────┘ -``` - -Full swipe: immediate deletion + Sonner toast "Deleted · Undo" (4 seconds). Partial swipe: snaps back. No confirmation dialog for single items. - -### Drag & Drop Reordering - -Handle (`grip-vertical` Lucide icon) on the left — always visible on desktop, revealed on long-press on mobile. Use `svelte-dnd-action`. During drag: lifted item has `shadow-md bg-white`, others compress to show insertion point. - -### Confirmations - -**Destructive, hard-to-reverse actions** (delete list, remove member, dissolve collective): `Dialog` with explicit description text. No shortcut. - -**Soft deletions** (single item, single task): swipe + Sonner undo. No dialog. - -### Avatar Component - -Three mutually exclusive states, all `rounded-full`. - -| State | Appearance | Size tokens | -|---|---|---| -| `initials` | Initials on `slate-700` bg, white 600-weight text | `size-6` presence, `size-8` lists, `size-16` settings | -| `emoji` | Emoji centered on `slate-700` bg | same | -| `upload` | ``, fallback to initials on error | same | - -Initials rule: first letter of first + last word in `display_name` ("Ana García" → "AG", "Borja" → "B"). - -### User Settings Screen (`/settings`) - -In the desktop sidebar shell. Content area: - -``` -← Settings - - ┌──────┐ - │ AG │ ← size-16 avatar - └──────┘ - Change avatar › - -DISPLAY NAME -┌────────────────────────────────┐ -│ Ana García │ ← auto-save, 500ms debounce -└────────────────────────────────┘ - Changes saved automatically ← slate-400 hint - -LANGUAGE - [ English ] [ Español ] ← pill segmented control - English selected: slate-900/white. Instant effect, no reload. - -ACCOUNT ← background shift (no line) - Email & password → ← external link to Keycloak - Managed in Keycloak ← slate-400 - -DANGER ZONE ← section label in red-500 - [ Delete account ] ← ghost red button -``` - -**Avatar sheet** — `Sheet` (bottom on mobile, dialog on desktop): - -- Segmented tabs: Initials | Emoji | Photo -- Emoji grid: 8 columns, ~40 emoji (🐸 🐼 🦊 🐨 🦁 🐯 🐻 🦋 🌵 🌻 🍀 🌈 ⭐ 🔥 💎 🎯 🎸 🎹 🎨 📚 ✈️ 🏔️ 🌊 🎭 🍕 🍜 🍣 ☕ 🍎 🥑 🍓 🎂 🏠 🚲 🛹 ⚽ 🏄 🧘 🎮 🤿) -- Photo upload: file picker → 1:1 crop in-browser → preview → confirm. Max 2MB. JPG/PNG/WebP. - -### Empty States - -Text-only, no illustrations. `text-slate-500`, centered in content area. - -``` -No lists yet. -Tap + to create one. -``` - -Two lines max. - -### Loading States - -`Skeleton` components matching the shape of the content (list rows, card grids). Spinner only for full-screen blocking operations (login, initial data fetch). - + +## 6. Do's and Don'ts + +### Do +- **Do** use uppercase labels with letter-spacing for all category headers. +- **Do** use `slate-500` for meta-text to create a visual "recession" of less important data. +- **Do** maximize the use of the `2xl` max-width container on desktop to ensure the "Editorial" line lengths remain readable. +- **Do** use Lucide icons at 1.5 stroke width to match the weight of Inter's medium-weight characters. + +### Don't +- **Don't** use a 1px border to separate the sidebar from the main content. Use a background color shift. +- **Don't** use any colors outside of the Slate and Red-500 palette. No "Success Green" or "Warning Orange." Use icons and text to convey state. +- **Don't** use standard "drop shadows" on cards. If it doesn't pop via tonal shift, the layout needs more whitespace, not more shadow. +- **Don't** use "Slate-900" for body text. Use "Slate-900" only for titles; use "Slate-800" or "Slate-700" for long-form reading to reduce eye strain. + --- - -## Stitch Mockup Inventory - -Screens in `.stitch/designs/` — project `10316144241804324155`: - -| File | Screen | Status | -|---|---|---| -| `01-lists-view.png` | Lists View | ✅ | -| `02-shopping-session.png` | Shopping Session | ✅ | -| `03-notes-view.png` | Notes View | ✅ | -| `04-tasks-view.png` | Tasks View | ✅ | -| `05-list-detail.png` | List Detail with frequency chips | ✅ | -| — | User Settings | ⏳ pending generation | -| — | Collective Management | ⏳ pending generation | + +## 7. Layout Philosophy +- **Desktop (lg+):** The left sidebar is a fixed Monolith. It stays `surface-raised` while the main content area remains `surface`. +- **Mobile:** The bottom tab bar utilizes Glassmorphism. Navigation icons should be `slate-400`, switching to `slate-900` when active. No text labels on mobile tabs; the icons must be clear enough to stand alone. \ No newline at end of file diff --git a/Justfile b/Justfile index 684bf50..4fca316 100644 --- a/Justfile +++ b/Justfile @@ -78,6 +78,11 @@ kc-open: build: pnpm turbo run build +# Run the production build locally against the dev Docker stack (port 3000) +serve: build + {{dc_dev}} up -d + PORT=3000 node apps/web/build/index.js + # Type-check all packages check: pnpm turbo run check diff --git a/README.md b/README.md index 3c37966..1825a09 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ colectivo/ ```bash just setup # preparación del entorno local (idempotente, seguro relanzar) just dev # levanta docker-compose.dev.yml + SvelteKit dev server +just serve # build + servidor Node prod en :3000 contra el stack Docker dev just stop # para todos los servicios (Docker stack + servidor Vite) just dev-stop # para solo el stack de Docker just dev-clean # para Docker y elimina todos los volúmenes (reset completo) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 0e41323..16850a0 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -5,6 +5,7 @@ "nav_notes": "Notes", "nav_search": "Search", "nav_settings": "Settings", + "nav_collective": "Collective", "app_name": "Colectivo", "loading": "Loading…", "error_generic": "Something went wrong. Please try again.", diff --git a/apps/web/messages/es.json b/apps/web/messages/es.json index 9ee7ea5..b26684a 100644 --- a/apps/web/messages/es.json +++ b/apps/web/messages/es.json @@ -5,6 +5,7 @@ "nav_notes": "Notas", "nav_search": "Buscar", "nav_settings": "Ajustes", + "nav_collective": "Colectivo", "app_name": "Colectivo", "loading": "Cargando…", "error_generic": "Algo salió mal. Por favor, inténtalo de nuevo.", diff --git a/apps/web/package.json b/apps/web/package.json index af23fbf..3a13c29 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -18,6 +18,7 @@ "@supabase/supabase-js": "^2.46.2", "cropperjs": "^1.6.2", "idb": "^8.0.1", + "lucide-svelte": "^1.0.1", "svelte-dnd-action": "^0.9.51" }, "devDependencies": { diff --git a/apps/web/src/app.css b/apps/web/src/app.css index 3a74129..977f71e 100644 --- a/apps/web/src/app.css +++ b/apps/web/src/app.css @@ -5,26 +5,31 @@ /* Inter font */ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); -/* Design tokens — light mode */ +/* Design tokens — light mode + * Surface hierarchy (stacked sheets of paper): + * background → surface-container-low → surface → surface-raised (cards pop up) + */ :root { - --background: 248 250 252; /* slate-50 */ - --surface: 255 255 255; /* white */ - --surface-raised: 241 245 249; /* slate-100 */ - --border: 226 232 240; /* slate-200 */ - --text-primary: 15 23 42; /* slate-900 */ - --text-secondary: 100 116 139; /* slate-500 */ - --text-muted: 148 163 184; /* slate-400 */ + --background: 247 249 251; /* #f7f9fb — spec exact value */ + --surface-container-low: 241 245 249; /* slate-100 — subtle grouping bg */ + --surface: 255 255 255; /* white — base card surface */ + --surface-raised: 248 250 252; /* slate-50 — popped card (sidebar) */ + --text-primary: 15 23 42; /* slate-900 — titles only */ + --text-secondary: 100 116 139; /* slate-500 — meta / labels */ + --text-muted: 148 163 184; /* slate-400 */ + --destructive: 185 28 28; /* red-700 ≈ #ba1a1a spec */ } /* Design tokens — dark mode */ .dark { - --background: 15 23 42; /* slate-950 */ - --surface: 30 41 59; /* slate-800 → closer to slate-900 card */ - --surface-raised: 51 65 85; /* slate-700 */ - --border: 51 65 85; /* slate-700 */ - --text-primary: 248 250 252; /* slate-50 */ - --text-secondary: 148 163 184; /* slate-400 */ - --text-muted: 100 116 139; /* slate-500 */ + --background: 2 6 23; /* #020617 slate-950 — spec exact value */ + --surface-container-low: 15 23 42; /* slate-900 — subtle grouping */ + --surface: 30 41 59; /* slate-800 — base card surface */ + --surface-raised: 51 65 85; /* slate-700 — popped card (sidebar) */ + --text-primary: 248 250 252; /* slate-50 */ + --text-secondary: 148 163 184; /* slate-400 */ + --text-muted: 100 116 139; /* slate-500 */ + --destructive: 239 68 68; /* red-500 — more visible on dark bg */ } * { @@ -38,8 +43,8 @@ body { body { font-family: 'Inter', system-ui, sans-serif; - background-color: hsl(var(--background)); - color: hsl(var(--text-primary)); + background-color: rgb(var(--background)); + color: rgb(var(--text-primary)); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } diff --git a/apps/web/src/routes/(app)/+layout.svelte b/apps/web/src/routes/(app)/+layout.svelte index b301c22..91e06f5 100644 --- a/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/web/src/routes/(app)/+layout.svelte @@ -9,6 +9,7 @@ import { logout } from '$lib/auth'; import Avatar from '$lib/components/Avatar.svelte'; import * as m from '$lib/paraglide/messages'; + import { ShoppingCart, CheckSquare, FileText, Search, Settings } from 'lucide-svelte'; // When auth resolves as unauthenticated, redirect to Keycloak. // This runs after onAuthStateChange fires (authLoading = false), so there @@ -20,10 +21,10 @@ }); const navItems = [ - { href: '/lists', label: () => m.nav_lists(), icon: '🛒' }, - { href: '/tasks', label: () => m.nav_tasks(), icon: '✓' }, - { href: '/notes', label: () => m.nav_notes(), icon: '📄' }, - { href: '/search', label: () => m.nav_search(), icon: '🔍' } + { href: '/lists', label: () => m.nav_lists(), icon: ShoppingCart }, + { href: '/tasks', label: () => m.nav_tasks(), icon: CheckSquare }, + { href: '/notes', label: () => m.nav_notes(), icon: FileText }, + { href: '/search', label: () => m.nav_search(), icon: Search } ]; let collectiveSwitcherOpen = $state(false); @@ -46,14 +47,14 @@ {:else if $isAuthenticated}
- -
diff --git a/apps/web/src/routes/(app)/collective/manage/+page.svelte b/apps/web/src/routes/(app)/collective/manage/+page.svelte index 04a977f..a9a18fa 100644 --- a/apps/web/src/routes/(app)/collective/manage/+page.svelte +++ b/apps/web/src/routes/(app)/collective/manage/+page.svelte @@ -136,25 +136,32 @@ } -
-

+
+
+
+

+ {m.nav_collective()} +

+

{m.manage_title()}

+
+
{#if error}

{error}

{/if} - +
-

+

{m.manage_members()}

{#if loading}

{m.loading()}

{:else} -
    +
      {#each members as member}
    • - + {#if isAdmin} -
      -

      +
      +

      {m.manage_invite_title()}

      @@ -232,8 +239,7 @@ {#if generatedLink} -
      +
      {generatedLink} @@ -250,3 +256,5 @@
      {/if}

+
+
diff --git a/apps/web/src/routes/(app)/lists/+page.svelte b/apps/web/src/routes/(app)/lists/+page.svelte index b380755..37c4ace 100644 --- a/apps/web/src/routes/(app)/lists/+page.svelte +++ b/apps/web/src/routes/(app)/lists/+page.svelte @@ -3,10 +3,15 @@
-
-

{m.lists_title()}

+
+

+ {m.nav_lists()} +

+

+ {m.lists_title()} +

-
+
diff --git a/apps/web/src/routes/(app)/notes/+page.svelte b/apps/web/src/routes/(app)/notes/+page.svelte index 455ddde..a7c9d14 100644 --- a/apps/web/src/routes/(app)/notes/+page.svelte +++ b/apps/web/src/routes/(app)/notes/+page.svelte @@ -3,10 +3,15 @@
-
-

{m.notes_title()}

+
+

+ {m.nav_notes()} +

+

+ {m.notes_title()} +

-
+
diff --git a/apps/web/src/routes/(app)/search/+page.svelte b/apps/web/src/routes/(app)/search/+page.svelte index 863e461..7894128 100644 --- a/apps/web/src/routes/(app)/search/+page.svelte +++ b/apps/web/src/routes/(app)/search/+page.svelte @@ -3,10 +3,15 @@
-
-

{m.search_title()}

+
+

+ {m.nav_search()} +

+

+ {m.search_title()} +

-
+
diff --git a/apps/web/src/routes/(app)/settings/+page.svelte b/apps/web/src/routes/(app)/settings/+page.svelte index e38d6e2..47f841e 100644 --- a/apps/web/src/routes/(app)/settings/+page.svelte +++ b/apps/web/src/routes/(app)/settings/+page.svelte @@ -143,11 +143,16 @@
-
-

{m.settings_title()}

+
+

+ {m.nav_settings()} +

+

+ {m.settings_title()} +

-
+
@@ -163,7 +168,7 @@
-