feat: major update with network graphs, themes, todo extensions, and more

## New Features

### Network Graph Visualization (Contacts, Calendar, Todo)
- D3.js force simulation for physics-based layout
- Zoom & pan with mouse/touchpad
- Keyboard shortcuts: +/- zoom, 0 reset, Esc deselect, / search, F focus
- Filtering by tags, company/location/project, connection strength
- Shared components in @manacore/shared-ui

### Central Tags API (mana-core-auth)
- CRUD endpoints for tags
- Schema: tags table with userId, name, color, app
- Shared tag components in @manacore/shared-ui

### Custom Themes System
- Theme editor with live preview and color picker
- Community theme gallery
- Theme sharing (public, unlisted, private)
- Backend API in mana-core-auth

### Todo App Extensions
- Glass-pill design for task input and items
- Settings page with 20+ preferences
- Task edit modal with inline editing
- Statistics page with visualizations
- PWA support with offline capabilities
- Multiple kanban boards

### Contacts App Features
- Duplicate detection
- Photo upload
- Batch operations
- Enhanced favorites page with multiple view modes
- Alphabet view improvements
- Search modal

### Help System
- @manacore/shared-help-content
- @manacore/shared-help-ui
- @manacore/shared-help-types

### Other Features
- Themes page for all apps
- Referral system frontend
- CommandBar (global search)
- Skeleton loaders
- Settings page improvements

## Bug Fixes
- Network graph simulation initialization
- Database schema TEXT for user_id columns (Better Auth compatibility)
- Various styling fixes

## Documentation
- Daily report for 2025-12-10
- CI/CD deployment guide

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-12-10 02:37:46 +01:00
parent e84371aa94
commit ee42b6cc76
381 changed files with 39284 additions and 6275 deletions

View file

@ -0,0 +1,24 @@
<script lang="ts">
/**
* SkeletonAvatar - Circular skeleton for profile pictures/avatars
*
* @example
* ```svelte
* <SkeletonAvatar size="40px" />
* <SkeletonAvatar size="64px" />
* ```
*/
import SkeletonBox from './SkeletonBox.svelte';
interface Props {
/** Size of the avatar (width & height) */
size?: string;
/** Additional CSS classes */
class?: string;
}
let { size = '40px', class: className = '' }: Props = $props();
</script>
<SkeletonBox width={size} height={size} circle class={className} />

View file

@ -0,0 +1,69 @@
<script lang="ts">
/**
* SkeletonCard - Configurable card skeleton with avatar, title, body, footer
*
* @example
* ```svelte
* <SkeletonCard showAvatar titleLines={1} bodyLines={2} />
* <SkeletonCard showFooter />
* ```
*/
import SkeletonBox from './SkeletonBox.svelte';
import SkeletonText from './SkeletonText.svelte';
import SkeletonAvatar from './SkeletonAvatar.svelte';
interface Props {
/** Show avatar/image placeholder */
showAvatar?: boolean;
/** Avatar size */
avatarSize?: string;
/** Number of title lines */
titleLines?: number;
/** Number of body text lines */
bodyLines?: number;
/** Show footer section */
showFooter?: boolean;
/** Opacity for fade effect in lists */
opacity?: number;
/** Additional CSS classes */
class?: string;
}
let {
showAvatar = false,
avatarSize = '48px',
titleLines = 1,
bodyLines = 2,
showFooter = false,
opacity = 1,
class: className = '',
}: Props = $props();
</script>
<div
class="skeleton-card rounded-lg border border-border bg-card p-4 {className}"
style="opacity: {opacity};"
>
<div class="flex gap-3">
{#if showAvatar}
<SkeletonAvatar size={avatarSize} />
{/if}
<div class="flex-1 min-w-0">
{#if titleLines > 0}
<SkeletonText lines={titleLines} lineHeight="18px" gap="6px" lastLineWidth="60%" />
{/if}
{#if bodyLines > 0}
<div class="mt-2">
<SkeletonText lines={bodyLines} lineHeight="14px" gap="6px" lastLineWidth="80%" />
</div>
{/if}
</div>
</div>
{#if showFooter}
<div class="mt-4 flex items-center justify-between border-t border-border pt-4">
<SkeletonBox width="80px" height="14px" />
<SkeletonBox width="60px" height="14px" />
</div>
{/if}
</div>

View file

@ -0,0 +1,61 @@
<script lang="ts">
/**
* SkeletonGrid - Grid of skeleton cards with fade effect
*
* @example
* ```svelte
* <SkeletonGrid count={6} columns={3} />
* <SkeletonGrid count={8} columns={4} showAvatar />
* ```
*/
import SkeletonCard from './SkeletonCard.svelte';
interface Props {
/** Number of cards to show */
count?: number;
/** Number of columns (CSS grid) */
columns?: number;
/** Show avatar in cards */
showAvatar?: boolean;
/** Avatar size */
avatarSize?: string;
/** Number of body lines per card */
bodyLines?: number;
/** Apply cascading fade effect */
fadeEffect?: boolean;
/** Minimum opacity for fade effect */
minOpacity?: number;
/** Gap between cards */
gap?: string;
/** Additional CSS classes */
class?: string;
}
let {
count = 6,
columns = 3,
showAvatar = true,
avatarSize = '48px',
bodyLines = 2,
fadeEffect = true,
minOpacity = 0.4,
gap = '1rem',
class: className = '',
}: Props = $props();
function calculateOpacity(index: number): number {
if (!fadeEffect) return 1;
const fadeStep = (1 - minOpacity) / Math.max(count - 1, 1);
return Math.max(minOpacity, 1 - index * fadeStep);
}
</script>
<div
class="skeleton-grid grid {className}"
style="grid-template-columns: repeat({columns}, minmax(0, 1fr)); gap: {gap};"
>
{#each Array(count) as _, i}
<SkeletonCard {showAvatar} {avatarSize} {bodyLines} opacity={calculateOpacity(i)} />
{/each}
</div>

View file

@ -0,0 +1,52 @@
<script lang="ts">
/**
* SkeletonList - List of skeleton rows with cascading fade effect
*
* @example
* ```svelte
* <SkeletonList count={5} />
* <SkeletonList count={10} showAvatar fadeEffect />
* ```
*/
import SkeletonRow from './SkeletonRow.svelte';
interface Props {
/** Number of rows to show */
count?: number;
/** Show avatar in each row */
showAvatar?: boolean;
/** Avatar size */
avatarSize?: string;
/** Apply cascading fade effect (rows fade out towards bottom) */
fadeEffect?: boolean;
/** Minimum opacity for fade effect */
minOpacity?: number;
/** Gap between rows */
gap?: string;
/** Additional CSS classes */
class?: string;
}
let {
count = 5,
showAvatar = true,
avatarSize = '40px',
fadeEffect = true,
minOpacity = 0.3,
gap = '0',
class: className = '',
}: Props = $props();
function calculateOpacity(index: number): number {
if (!fadeEffect) return 1;
const fadeStep = (1 - minOpacity) / Math.max(count - 1, 1);
return Math.max(minOpacity, 1 - index * fadeStep);
}
</script>
<div class="skeleton-list flex flex-col {className}" style="gap: {gap};">
{#each Array(count) as _, i}
<SkeletonRow {showAvatar} {avatarSize} opacity={calculateOpacity(i)} />
{/each}
</div>

View file

@ -0,0 +1,60 @@
<script lang="ts">
/**
* SkeletonRow - Single row skeleton with avatar and text
*
* @example
* ```svelte
* <SkeletonRow showAvatar />
* <SkeletonRow opacity={0.5} />
* ```
*/
import SkeletonBox from './SkeletonBox.svelte';
import SkeletonAvatar from './SkeletonAvatar.svelte';
interface Props {
/** Show avatar placeholder */
showAvatar?: boolean;
/** Avatar size */
avatarSize?: string;
/** Opacity for fade effect */
opacity?: number;
/** Show secondary line */
showSecondaryLine?: boolean;
/** Show right-side content */
showRightContent?: boolean;
/** Additional CSS classes */
class?: string;
}
let {
showAvatar = true,
avatarSize = '40px',
opacity = 1,
showSecondaryLine = true,
showRightContent = true,
class: className = '',
}: Props = $props();
</script>
<div
class="skeleton-row flex items-center gap-3 border-b border-border px-4 py-3 {className}"
style="opacity: {opacity};"
role="status"
aria-label="Loading"
>
{#if showAvatar}
<SkeletonAvatar size={avatarSize} />
{/if}
<div class="flex-1 min-w-0">
<SkeletonBox width="45%" height="16px" />
{#if showSecondaryLine}
<div class="mt-1.5">
<SkeletonBox width="65%" height="13px" />
</div>
{/if}
</div>
{#if showRightContent}
<SkeletonBox width="70px" height="13px" />
{/if}
</div>

View file

@ -1,5 +1,25 @@
/**
* Loading state components
*
* Primitives:
* - SkeletonBox: Base rectangular skeleton with shimmer
* - SkeletonText: Multi-line text skeleton
* - SkeletonAvatar: Circular avatar skeleton
*
* Composites:
* - SkeletonRow: Single list row with avatar + text
* - SkeletonList: Multiple rows with fade effect
* - SkeletonCard: Card with avatar, title, body, footer
* - SkeletonGrid: Grid of cards with fade effect
*/
// Primitives
export { default as SkeletonBox } from './SkeletonBox.svelte';
export { default as SkeletonText } from './SkeletonText.svelte';
export { default as SkeletonAvatar } from './SkeletonAvatar.svelte';
// Composites
export { default as SkeletonRow } from './SkeletonRow.svelte';
export { default as SkeletonList } from './SkeletonList.svelte';
export { default as SkeletonCard } from './SkeletonCard.svelte';
export { default as SkeletonGrid } from './SkeletonGrid.svelte';