diff --git a/backend/leads/serializer.py b/backend/leads/serializer.py
index fa898e85..4fa35637 100644
--- a/backend/leads/serializer.py
+++ b/backend/leads/serializer.py
@@ -66,6 +66,7 @@ class LeadSerializer(serializers.ModelSerializer):
# System
"created_by",
"created_at",
+ "updated_at",
"is_active",
"company_name",
# Kanban
diff --git a/frontend/src/app.css b/frontend/src/app.css
index 7824963c..2e39d816 100644
--- a/frontend/src/app.css
+++ b/frontend/src/app.css
@@ -65,14 +65,17 @@
--chart-5: #fed7aa;
/* Sidebar */
- --sidebar: oklch(0.995 0 0);
- --sidebar-foreground: oklch(0.25 0.02 60);
+ --sidebar: oklch(1 0 0);
+ --sidebar-foreground: oklch(0.18 0 0);
--sidebar-primary: #ea580c;
--sidebar-primary-foreground: oklch(0.995 0 0);
- --sidebar-accent: oklch(0.97 0.01 60);
- --sidebar-accent-foreground: oklch(0.2 0.02 60);
- --sidebar-border: oklch(0.94 0.005 60);
+ --sidebar-accent: oklch(0.96 0 0);
+ --sidebar-accent-foreground: oklch(0.18 0 0);
+ --sidebar-border: oklch(0.94 0 0);
--sidebar-ring: #ea580c;
+ --sidebar-active-fill: oklch(0.94 0 0);
+ --sidebar-muted: oklch(0.55 0 0);
+ --sidebar-subtle: oklch(0.65 0 0);
/* ─────────────────────────────────────────────────────────────────────────
PRIMARY COLOR SCALE
@@ -349,9 +352,9 @@
/* ─────────────────────────────────────────────────────────────────────────
LAYOUT DIMENSIONS
───────────────────────────────────────────────────────────────────────── */
- --sidebar-width: 260px;
+ --sidebar-width: 248px;
--sidebar-width-collapsed: 72px;
- --sidebar-width-icon: 3rem;
+ --sidebar-width-icon: 56px;
--drawer-width: 520px;
--header-height: 60px;
--content-padding: 28px;
@@ -409,6 +412,63 @@
--status-danger-bg: #fdeaea;
--status-info: #17a2b8;
--status-info-bg: #e3f2fd;
+
+ /* ─────────────────────────────────────────────────────────────────────────
+ REDESIGN TOKENS (2026-05-10 spec) — Phase 1
+ New design system, additive. Existing tokens above are kept for
+ back-compat until Phase 2+ migrates components onto these.
+ Spec: docs/superpowers/specs/2026-05-10-app-redesign-design.md
+ ───────────────────────────────────────────────────────────────────────── */
+
+ /* Surface */
+ --bg: #ffffff;
+ --bg-page: #fafafa;
+ --bg-card: #ffffff;
+ --bg-elevated: #fafafa;
+ --bg-hover: #f5f5f4;
+ --bg-active: #f0efed;
+ --bg-input: #ffffff;
+
+ /* Border (NOTE: --border, --border-strong, --focus-ring redefined — see Phase 1 plan §Task 3) */
+ --border-faint: #f0f0f0;
+ --border: #e5e5e5;
+ --border-strong: #c4c4c4;
+ --focus-ring: rgba(17, 17, 17, 0.10);
+
+ /* Text */
+ --text: #111111;
+ --text-muted: #525252;
+ --text-subtle: #999999;
+
+ /* Accents — semantic */
+ --green: #10b981;
+ --green-soft: rgba(16, 185, 129, 0.12);
+ --green-soft-text: #047857;
+ --violet: #7c3aed;
+ --violet-soft: rgba(124, 58, 237, 0.10);
+ --violet-soft-text: #6d28d9;
+ --amber-soft: rgba(217, 119, 6, 0.10);
+ --amber-soft-text: #92400e;
+ --red: #dc2626;
+ --red-soft: rgba(220, 38, 38, 0.08);
+ --orange: #ea580c;
+
+ /* CTA */
+ --cta-bg: #111111;
+ --cta-text: #ffffff;
+ --cta-bg-hover: #000000;
+
+ /* Radii */
+ --r-sm: 5px;
+ --r-md: 7px;
+ --r-lg: 10px;
+ --r-xl: 12px;
+ --r-full: 9999px;
+
+ /* Fonts (override existing aliases at lines ~379–381) */
+ --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, ui-sans-serif, sans-serif;
+ --font-mono: 'JetBrains Mono', ui-monospace, monospace;
+ --font-body: 'Inter', -apple-system, BlinkMacSystemFont, ui-sans-serif, sans-serif;
}
/* ═══════════════════════════════════════════════════════════════════════════
@@ -457,14 +517,17 @@
--chart-5: #fff7ed;
/* Sidebar */
- --sidebar: oklch(0.16 0.01 30);
- --sidebar-foreground: oklch(0.92 0.005 60);
+ --sidebar: oklch(0.14 0 0);
+ --sidebar-foreground: oklch(0.92 0 0);
--sidebar-primary: #fb923c;
--sidebar-primary-foreground: oklch(0.12 0.01 30);
- --sidebar-accent: oklch(0.22 0.015 30);
- --sidebar-accent-foreground: oklch(0.92 0.005 60);
- --sidebar-border: oklch(0.28 0.01 30);
+ --sidebar-accent: rgba(255, 255, 255, 0.06);
+ --sidebar-accent-foreground: oklch(0.92 0 0);
+ --sidebar-border: rgba(255, 255, 255, 0.08);
--sidebar-ring: #fb923c;
+ --sidebar-active-fill: rgba(255, 255, 255, 0.10);
+ --sidebar-muted: rgba(255, 255, 255, 0.65);
+ --sidebar-subtle: rgba(255, 255, 255, 0.45);
/* Primary color scale - Dark mode */
--color-primary-default: #fb923c;
@@ -572,6 +635,50 @@
--glass-bg: rgba(18, 19, 24, 0.8);
--glass-border: rgba(255, 255, 255, 0.08);
--glass-blur: 16px;
+
+ /* ─────────────────────────────────────────────────────────────────────────
+ REDESIGN TOKENS (2026-05-10 spec) — Phase 1, dark theme overrides
+ ───────────────────────────────────────────────────────────────────────── */
+
+ /* Surface */
+ --bg: #000000;
+ --bg-page: #000000;
+ --bg-card: #0a0a0b;
+ --bg-elevated: #131316;
+ --bg-hover: #18181c;
+ --bg-active: #1f1f23;
+ --bg-input: #131316;
+
+ /* Border */
+ --border-faint: rgba(255, 255, 255, 0.06);
+ --border: rgba(255, 255, 255, 0.10);
+ --border-strong: rgba(255, 255, 255, 0.20);
+ --focus-ring: rgba(237, 242, 245, 0.14);
+
+ /* Text */
+ --text: #edf2f5;
+ --text-muted: #828282;
+ --text-subtle: #555555;
+
+ /* Accents — semantic */
+ --green: #10b981;
+ --green-soft: rgba(16, 185, 129, 0.14);
+ --green-soft-text: #6ee7b7;
+ --violet: #7c7cff;
+ --violet-soft: rgba(124, 124, 255, 0.14);
+ --violet-soft-text: #c4c4ff;
+ --amber-soft: rgba(217, 119, 6, 0.18);
+ --amber-soft-text: #fcd34d;
+ --red: #f87171;
+ --red-soft: rgba(248, 113, 113, 0.10);
+ --orange: #ea580c;
+
+ /* CTA (inverted) */
+ --cta-bg: #ffffff;
+ --cta-text: #18181b;
+ --cta-bg-hover: #ededed;
+
+ /* Radii + fonts inherit from :root — no override needed */
}
/* ═══════════════════════════════════════════════════════════════════════════
@@ -1013,57 +1120,16 @@ select {
transform 0.2s ease;
}
-/* Menu button refinements */
-[data-slot='sidebar-menu-button'] {
- letter-spacing: -0.01em;
-}
-
-/* Active state with left accent bar */
+/* Required for the dot-indicator absolute positioning */
[data-slot='sidebar-menu-button'][data-active='true'] {
position: relative;
}
-/* Smooth hover transitions */
-[data-slot='sidebar-menu-button']:hover {
- transform: translateX(1px);
-}
-
-[data-slot='sidebar-menu-button'][data-active='true']:hover {
- transform: none;
-}
-
-/* Group label styling - uppercase sections */
-[data-slot='sidebar-group-label'] {
- font-size: 10px;
- font-weight: 700;
- text-transform: uppercase;
- letter-spacing: 0.08em;
- color: var(--sidebar-foreground);
- opacity: 0.4;
- padding: 0 12px;
- margin-bottom: 4px;
-}
-
-/* Submenu refinements */
-[data-slot='sidebar-menu-sub'] {
- margin-top: 4px;
-}
-
-/* Rail hover effect - Orange accent */
-[data-slot='sidebar-rail']:hover {
- background: linear-gradient(90deg, transparent, rgba(234, 88, 12, 0.1));
-}
-
/* Collapsed state icon centering */
[data-collapsible='icon'] [data-slot='sidebar-menu-button'] {
justify-content: center;
}
-/* Footer divider */
-[data-slot='sidebar-footer'] {
- background: linear-gradient(180deg, transparent, var(--sidebar) 8px);
-}
-
/* ═══════════════════════════════════════════════════════════════════════════
KANBAN BOARD STYLES
═══════════════════════════════════════════════════════════════════════════ */
@@ -1116,3 +1182,90 @@ select {
background: var(--color-primary-light);
border-left: 3px solid var(--color-primary-default);
}
+
+@layer utilities {
+ /* ─────────────────────────────────────────────────────────────────────────
+ REDESIGN TYPOGRAPHY UTILITIES (2026-05-10 spec) — Phase 1
+ See spec §5 for usage. Tabular numerics applied where data-heavy.
+ ───────────────────────────────────────────────────────────────────────── */
+
+ .h-display {
+ font-size: 28px;
+ font-weight: 500;
+ letter-spacing: -0.035em;
+ color: var(--text);
+ }
+
+ .h-page {
+ font-size: 22px;
+ font-weight: 500;
+ letter-spacing: -0.03em;
+ color: var(--text);
+ }
+
+ .h-section {
+ font-size: 16px;
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ color: var(--text);
+ }
+
+ .t-body {
+ font-size: 13.5px;
+ font-weight: 400;
+ color: var(--text);
+ }
+
+ .t-body-sm {
+ font-size: 12.5px;
+ font-weight: 400;
+ color: var(--text-muted);
+ }
+
+ .t-meta {
+ font-size: 11.5px;
+ font-weight: 400;
+ color: var(--text-subtle);
+ }
+
+ .label-section {
+ font-size: 11px;
+ font-weight: 500;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--text-subtle);
+ }
+
+ .label-tiny {
+ font-size: 10px;
+ font-weight: 500;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--text-subtle);
+ }
+
+ .kbd {
+ font-family: var(--font-mono);
+ font-size: 10px;
+ font-weight: 500;
+ color: var(--text-muted);
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-faint);
+ border-radius: 3px;
+ padding: 1px 4px;
+ }
+
+ /* Apply tabular numerics globally on a helper class — useful for tables, KPI cards */
+ .tabular-nums {
+ font-variant-numeric: tabular-nums;
+ }
+}
+
+/* Inter variant features (cv11 = sharper digits, ss01 = alternate single-storey 'a').
+ Applied at root so all descendants inherit.
+ NOTE: This intentionally overrides the @layer base html { ... } rule earlier in this file.
+ Geist-specific features (cv02/cv03/cv04) are dropped since we're using Inter, not Geist.
+ When components fully migrate off Geist in Phase 2+, the older @layer base rule can be removed. */
+html {
+ font-feature-settings: 'cv11', 'ss01';
+}
diff --git a/frontend/src/app.html b/frontend/src/app.html
index 232f23ee..d6ed27e9 100644
--- a/frontend/src/app.html
+++ b/frontend/src/app.html
@@ -1,11 +1,11 @@
-
+
diff --git a/frontend/src/lib/components/layout/AppShell.svelte b/frontend/src/lib/components/layout/AppShell.svelte
index ed4b226f..42399169 100644
--- a/frontend/src/lib/components/layout/AppShell.svelte
+++ b/frontend/src/lib/components/layout/AppShell.svelte
@@ -6,11 +6,12 @@
* @typedef {Object} Props
* @property {Object} user - User object with name, email, profilePhoto
* @property {string} [org_name] - Organization name
+ * @property {Object} [org_settings] - Org settings (tier, currency, etc.)
* @property {import('svelte').Snippet} [children] - Child content
*/
/** @type {Props & { children?: import('svelte').Snippet }} */
- let { user = {}, org_name = 'BottleCRM', children } = $props();
+ let { user = {}, org_name = 'BottleCRM', org_settings = {}, children } = $props();
// Get initial sidebar state from cookie (set by server or previous session)
let defaultOpen = $state(true);
@@ -28,7 +29,7 @@
-
+
{@render children?.()}
diff --git a/frontend/src/lib/components/layout/AppSidebar.svelte b/frontend/src/lib/components/layout/AppSidebar.svelte
index d1364559..dfa947de 100644
--- a/frontend/src/lib/components/layout/AppSidebar.svelte
+++ b/frontend/src/lib/components/layout/AppSidebar.svelte
@@ -2,12 +2,12 @@
import { untrack } from 'svelte';
import { page } from '$app/stores';
import { afterNavigate, goto } from '$app/navigation';
- import imgLogo from '$lib/assets/images/logo.png';
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu/index.js';
import * as Collapsible from '$lib/components/ui/collapsible/index.js';
import { Button } from '$lib/components/ui/button/index.js';
+ import { Avatar, AvatarFallback, gradientFor } from '$lib/components/ui/avatar/index.js';
import {
LayoutDashboard,
@@ -47,17 +47,27 @@
Clock,
MessageSquareQuote,
ShieldCheck,
- BookOpen
+ BookOpen,
+ Activity,
+ Smartphone
} from '@lucide/svelte';
+ import { Bell } from '$lib/components/notifications/index.js';
/**
* @typedef {Object} Props
* @property {Object} user - User object
* @property {string} [org_name] - Organization name
+ * @property {Object} [org_settings] - Org settings (tier, currency, etc.)
*/
/** @type {Props} */
- let { user = {}, org_name = 'BottleCRM' } = $props();
+ let { user = {}, org_name = 'BottleCRM', org_settings = {} } = $props();
+
+ // Tier badge — hidden when JWT doesn't carry one (spec §8 "No tier in JWT")
+ const tier = $derived(org_settings?.tier ?? null);
+
+ // Workspace gradient seed — first-letter monogram fallback for the avatar
+ const orgInitial = $derived((org_name || 'B').charAt(0).toUpperCase());
const sidebar = Sidebar.useSidebar();
@@ -163,107 +173,62 @@
}
});
- // HubSpot-style navigation structure with sections
- const crmItems = [
- {
- href: '/',
- label: 'Dashboard',
- icon: Home,
- type: 'link',
- preload: 'off'
- },
- {
- href: '/leads',
- label: 'Leads',
- icon: Target,
- type: 'link',
- preload: 'off'
- },
- {
- href: '/contacts',
- label: 'Contacts',
- icon: Users,
- type: 'link',
- preload: 'off'
- },
- {
- href: '/accounts',
- label: 'Accounts',
- icon: Building,
- type: 'link',
- preload: 'off'
- },
- {
- href: '/opportunities',
- label: 'Deals',
- icon: Sparkles,
- type: 'link',
- preload: 'off'
- },
- {
- href: '/goals',
- label: 'Goals',
- icon: Trophy,
- type: 'link',
- preload: 'off'
- },
+ // Five-section IA per spec §4
+ const workspaceItems = [
+ { href: '/', label: 'Home', icon: Home, type: 'link', preload: 'off', count: undefined },
+ { href: '/leads', label: 'Pipeline', icon: Activity, type: 'link', preload: 'off', count: undefined }
+ ];
+
+ const recordsItems = [
+ { href: '/leads', label: 'Leads', icon: Target, type: 'link', preload: 'off', count: undefined },
+ { href: '/contacts', label: 'Contacts', icon: Users, type: 'link', preload: 'off', count: undefined },
+ { href: '/accounts', label: 'Accounts', icon: Building, type: 'link', preload: 'off', count: undefined },
+ { href: '/opportunities', label: 'Deals', icon: Sparkles, type: 'link', preload: 'off', count: undefined }
+ ];
+
+ const workItems = [
{
key: 'tickets',
label: 'Tickets',
icon: Briefcase,
type: 'dropdown',
+ count: undefined,
children: [
- { href: '/tickets', label: 'All tickets', icon: Briefcase, preload: 'off' },
- { href: '/tickets/approvals', label: 'Approvals', icon: ShieldCheck, preload: 'off' },
- { href: '/tickets/analytics', label: 'Analytics', icon: BarChart3, preload: 'off' },
- { href: '/solutions', label: 'Knowledge Base', icon: BookOpen, preload: 'off' }
+ { href: '/tickets', label: 'All tickets', icon: Briefcase, preload: 'off', count: undefined },
+ { href: '/tickets/approvals', label: 'Approvals', icon: ShieldCheck, preload: 'off', count: undefined },
+ { href: '/tickets/analytics', label: 'Analytics', icon: BarChart3, preload: 'off', count: undefined },
+ { href: '/solutions', label: 'Knowledge base', icon: BookOpen, preload: 'off', count: undefined }
]
},
- {
- href: '/tasks',
- label: 'Tasks',
- icon: CheckSquare,
- type: 'link',
- preload: 'off'
- },
- {
- href: '/timesheet',
- label: 'Timesheet',
- icon: Clock,
- type: 'link',
- preload: 'off'
- }
+ { href: '/tasks', label: 'Tasks', icon: CheckSquare, type: 'link', preload: 'off', count: undefined },
+ { href: '/timesheet', label: 'Timesheet', icon: Clock, type: 'link', preload: 'off', count: undefined },
+ { href: '/goals', label: 'Goals', icon: Trophy, type: 'link', preload: 'off', count: undefined }
];
- const salesItems = [
+ const revenueItems = [
{
key: 'invoices',
label: 'Invoices',
icon: FileText,
type: 'dropdown',
+ count: undefined,
children: [
- { href: '/invoices', label: 'All Invoices', icon: FileText, preload: 'off' },
- { href: '/invoices/estimates', label: 'Estimates', icon: FileEdit, preload: 'off' },
- { href: '/invoices/products', label: 'Products', icon: Package, preload: 'off' },
- { href: '/invoices/recurring', label: 'Recurring', icon: RefreshCw, preload: 'off' },
- { href: '/invoices/templates', label: 'Templates', icon: FileCode, preload: 'off' },
- { href: '/invoices/reports', label: 'Reports', icon: BarChart3, preload: 'off' }
+ { href: '/invoices', label: 'All Invoices', icon: FileText, preload: 'off', count: undefined },
+ { href: '/invoices/estimates', label: 'Estimates', icon: FileEdit, preload: 'off', count: undefined },
+ { href: '/invoices/products', label: 'Products', icon: Package, preload: 'off', count: undefined },
+ { href: '/invoices/recurring', label: 'Recurring', icon: RefreshCw, preload: 'off', count: undefined },
+ { href: '/invoices/templates', label: 'Templates', icon: FileCode, preload: 'off', count: undefined },
+ { href: '/invoices/reports', label: 'Reports', icon: BarChart3, preload: 'off', count: undefined }
]
}
];
const supportItems = [
- {
- href: '/support',
- label: 'Help Desk',
- icon: HelpCircle,
- type: 'link',
- preload: 'off'
- }
+ { href: '/support', label: 'Help desk', icon: HelpCircle, type: 'link', preload: 'off', count: undefined }
];
- // Combine for iteration
- const navigationItems = [...crmItems, ...salesItems, ...supportItems];
+ // Combine for the auto-open-on-active effect (which scans dropdown items)
+ const navigationItems = [...workspaceItems, ...recordsItems, ...workItems, ...revenueItems, ...supportItems];
/**
* Check if any child route is active
@@ -288,46 +253,56 @@
collapsible="icon"
class="hubspot-sidebar border-sidebar-border/60 bg-sidebar border-r"
>
-
-
-
-
-
- {#snippet child({ props })}
-
-
-
+
-
-
+
+
-
- {#each crmItems as item}
+
+ {#each workspaceItems as item}
{#if item.type === 'dropdown' && item.children}
{#snippet child({ props: btnProps })}
-
-
+
+ {#if hasActiveChild(item.children ?? [])}
+
+ {/if}
+
{item.label}
+ class="flex-1 truncate text-[13px] group-data-[collapsible=icon]:hidden {hasActiveChild(item.children ?? []) ? 'font-semibold' : 'font-medium'}"
+ >{item.label}
+ {#if item.count !== undefined && item.count !== null}
+
+ {/if}
{/snippet}
@@ -366,28 +354,33 @@
-
-
+
+
-
- {#each salesItems as item}
+
+ {#each recordsItems as item}
{#if item.type === 'dropdown' && item.children}
{#snippet child({ props: btnProps })}
-
-
+
+ {#if hasActiveChild(item.children ?? [])}
+
+ {/if}
+
{item.label}
+ class="flex-1 truncate text-[13px] group-data-[collapsible=icon]:hidden {hasActiveChild(item.children ?? []) ? 'font-semibold' : 'font-medium'}"
+ >{item.label}
+ {#if item.count !== undefined && item.count !== null}
+
+ {/if}
{/snippet}
@@ -484,28 +497,33 @@
+ {:else}
+
+
+
+ {/if}
+ {/each}
+
+
+
+
+
+
+
+
+
+ {#each workItems as item}
+ {#if item.type === 'dropdown' && item.children}
+ {
+ if (item.key) openDropdowns[item.key] = open;
+ }}
+ class="group/collapsible"
+ >
+
+
+ {#snippet child({ props })}
+
+ {/snippet}
+
+
+
+ {#each item.children as navChild}
+
+
+
+ {/each}
+
+
+
+
+ {:else}
+
+
+
+ {/if}
+ {/each}
+
+
+
+
+
+
+
+
+
+ {#each revenueItems as item}
+ {#if item.type === 'dropdown' && item.children}
+ {
+ if (item.key) openDropdowns[item.key] = open;
+ }}
+ class="group/collapsible"
+ >
+
+
+ {#snippet child({ props })}
+
+ {/snippet}
+
+
+
+ {#each item.children as navChild}
+
+
+
+ {/each}
+
+
+
+
+ {:else}
+
+
+
{/if}
{/each}
@@ -522,70 +866,200 @@
-
+
-
+
{#each supportItems as item}
-
- {
+ if (item.key) openDropdowns[item.key] = open;
+ }}
+ class="group/collapsible"
>
- {#snippet child({ props })}
-
-
- {item.label}
+
+ {#snippet child({ props })}
+
+ {/snippet}
+
+
+
-
- {/snippet}
-
-
+ {#each item.children as navChild}
+
+
+
+ {/each}
+
+
+
+
+ {:else}
+
+
+
+ {/if}
{/each}
-