diff --git a/apps/manacore/apps/web/src/lib/components/workbench/app-registry.ts b/apps/manacore/apps/web/src/lib/components/workbench/app-registry.ts index a53b32293..c95c22b30 100644 --- a/apps/manacore/apps/web/src/lib/components/workbench/app-registry.ts +++ b/apps/manacore/apps/web/src/lib/components/workbench/app-registry.ts @@ -53,6 +53,12 @@ export const APP_REGISTRY: AppEntry[] = [ detail: { load: () => import('$lib/modules/contacts/views/DetailView.svelte') }, }, }, + { + id: 'habits', + name: 'Habits', + color: '#8B5CF6', + load: () => import('$lib/modules/habits/ListView.svelte'), + }, { id: 'chat', name: 'Chat', diff --git a/apps/manacore/apps/web/src/lib/data/database.ts b/apps/manacore/apps/web/src/lib/data/database.ts index 4ce645c98..20002e204 100644 --- a/apps/manacore/apps/web/src/lib/data/database.ts +++ b/apps/manacore/apps/web/src/lib/data/database.ts @@ -175,6 +175,10 @@ db.version(1).stores({ // ─── Playground (appId: 'playground') ─── // No persistent data — stateless LLM playground + // ─── Habits (appId: 'habits') ─── + habits: 'id, order, isArchived, color', + habitLogs: 'id, habitId, timestamp, [habitId+timestamp]', + // ─── Shared: Global Tags (appId: 'tags') ─── globalTags: 'id, name, groupId', tagGroups: 'id', @@ -223,6 +227,7 @@ export const SYNC_APP_MAP: Record = { moodlit: ['moods', 'sequences', 'moodTags'], memoro: ['memos', 'memories', 'memoTags', 'memoroSpaces', 'spaceMembers', 'memoSpaces'], guides: ['guides', 'sections', 'steps', 'guideCollections', 'runs', 'guideTags'], + habits: ['habits', 'habitLogs'], tags: ['globalTags', 'tagGroups'], links: ['manaLinks'], }; diff --git a/apps/manacore/apps/web/src/lib/modules/habits/ListView.svelte b/apps/manacore/apps/web/src/lib/modules/habits/ListView.svelte new file mode 100644 index 000000000..26bb2ba23 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/habits/ListView.svelte @@ -0,0 +1,543 @@ + + + +
+ +
+ {#each activeHabits as habit (habit.id)} + {@const count = todayCounts.get(habit.id) ?? 0} + {@const overTarget = habit.targetPerDay !== null && count >= habit.targetPerDay} + + {/each} + + + {#if !showCreate} + + {/if} +
+ + + {#if showCreate} +
+
+ + +
+ {#if showEmojiPicker} +
+ {#each QUICK_EMOJIS as e} + + {/each} +
+ {/if} +
+ {#each QUICK_COLORS as c} + + {/each} +
+
+ + +
+
+ {/if} + + + {#if todayLogs.length > 0} +
+
Heute
+ {#each todayLogs as log (log.id)} + {@const habit = habitMap.get(log.habitId)} + {#if habit} +
+ {habit.emoji} + {habit.title} + {formatTime(log.timestamp)} +
+ {/if} + {/each} +
+ {/if} + + {#if activeHabits.length === 0 && !showCreate} +
+

Noch keine Habits angelegt.

+ +
+ {/if} +
+ + diff --git a/apps/manacore/apps/web/src/lib/modules/habits/collections.ts b/apps/manacore/apps/web/src/lib/modules/habits/collections.ts new file mode 100644 index 000000000..0cf938f46 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/habits/collections.ts @@ -0,0 +1,49 @@ +/** + * Habits module — collection accessors and guest seed data. + * + * Tables are defined in the unified database.ts as: + * habits, habitLogs + */ + +import { db } from '$lib/data/database'; +import type { LocalHabit, LocalHabitLog } from './types'; + +// ─── Collection Accessors ────────────────────────────────── + +export const habitTable = db.table('habits'); +export const habitLogTable = db.table('habitLogs'); + +// ─── Guest Seed ──────────────────────────────────────────── + +export const HABITS_GUEST_SEED = { + habits: [ + { + id: 'habit-coffee', + title: 'Kaffee', + emoji: '\u2615', + color: '#f59e0b', + targetPerDay: 3, + order: 0, + isArchived: false, + }, + { + id: 'habit-water', + title: 'Wasser', + emoji: '\ud83d\udca7', + color: '#06b6d4', + targetPerDay: 8, + order: 1, + isArchived: false, + }, + { + id: 'habit-workout', + title: 'Workout', + emoji: '\ud83d\udcaa', + color: '#22c55e', + targetPerDay: 1, + order: 2, + isArchived: false, + }, + ] satisfies LocalHabit[], + habitLogs: [] satisfies LocalHabitLog[], +}; diff --git a/apps/manacore/apps/web/src/lib/modules/habits/components/DayTimeline.svelte b/apps/manacore/apps/web/src/lib/modules/habits/components/DayTimeline.svelte new file mode 100644 index 000000000..1d33b9484 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/habits/components/DayTimeline.svelte @@ -0,0 +1,122 @@ + + + +{#if dateLogs.length > 0} +
+
{formatDateLabel(date)}
+
+ {#each dateLogs as log (log.id)} + {@const habit = habitMap.get(log.habitId)} + {#if habit} +
+
+ {habit.emoji} + {habit.title} + {formatTime(log.timestamp)} + {#if log.note} + {log.note} + {/if} +
+ {/if} + {/each} +
+
+{/if} + + diff --git a/apps/manacore/apps/web/src/lib/modules/habits/components/HabitBoard.svelte b/apps/manacore/apps/web/src/lib/modules/habits/components/HabitBoard.svelte new file mode 100644 index 000000000..fff1b4bcc --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/habits/components/HabitBoard.svelte @@ -0,0 +1,103 @@ + + + +
+
+ {#each activeHabits as habit (habit.id)} + + {/each} + + + {#if !showCreate} + + {/if} +
+ + {#if showCreate} +
+ (showCreate = false)} onCancel={() => (showCreate = false)} /> +
+ {/if} +
+ + diff --git a/apps/manacore/apps/web/src/lib/modules/habits/components/HabitDetail.svelte b/apps/manacore/apps/web/src/lib/modules/habits/components/HabitDetail.svelte new file mode 100644 index 000000000..437cfd164 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/habits/components/HabitDetail.svelte @@ -0,0 +1,480 @@ + + + +
+ +
+ +
+ {habit.emoji} +

{habit.title}

+
+ +
+ + + {#if showEdit} + (showEdit = false)} onCancel={() => (showEdit = false)} /> + {/if} + + +
+
+ {todayCount} + Heute +
+
+ {streak} + Streak +
+
+ {totalLogs} + Gesamt +
+
+ + +
+
Letzte 7 Tage
+
+ {#each last7Days() as day} +
+
+
+
+ {day.count} + {formatDayLabel(day.date)} +
+ {/each} +
+
+ + + + + +
+

Verlauf

+ {#each [...groupedLogs.entries()] as [date, dayLogs] (date)} +
+
{formatDateLabel(date)}
+ {#each dayLogs as log (log.id)} +
+ {formatTime(log.timestamp)} + {#if log.note} + {log.note} + {/if} + +
+ {/each} +
+ {/each} + {#if habitLogs.length === 0} +

Noch keine Einträge. Tippe oben zum Loggen.

+ {/if} +
+ + +
+ + {#if !confirmDelete} + + {:else} + + {/if} +
+
+ + diff --git a/apps/manacore/apps/web/src/lib/modules/habits/components/HabitForm.svelte b/apps/manacore/apps/web/src/lib/modules/habits/components/HabitForm.svelte new file mode 100644 index 000000000..ac5369ccc --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/habits/components/HabitForm.svelte @@ -0,0 +1,291 @@ + + + +
+
+ + +
+ + {#if showEmojiPicker} +
+ {#each HABIT_EMOJIS as e} + + {/each} +
+ {/if} + +
+ {#each HABIT_COLORS as c} + + {/each} +
+ +
+ +
+ +
+ + +
+
+ + diff --git a/apps/manacore/apps/web/src/lib/modules/habits/components/HabitTile.svelte b/apps/manacore/apps/web/src/lib/modules/habits/components/HabitTile.svelte new file mode 100644 index 000000000..23bdc1467 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/habits/components/HabitTile.svelte @@ -0,0 +1,222 @@ + + + +
+ + +
+ + diff --git a/apps/manacore/apps/web/src/lib/modules/habits/entity.ts b/apps/manacore/apps/web/src/lib/modules/habits/entity.ts new file mode 100644 index 000000000..c66388d31 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/habits/entity.ts @@ -0,0 +1,35 @@ +import { registerEntity } from '$lib/entities/registry'; +import { habitsStore } from './stores/habits.svelte'; +import type { EntityDescriptor } from '$lib/entities/types'; + +const habitsEntity: EntityDescriptor = { + appId: 'habits', + collection: 'habits', + + getDisplayData: (item) => ({ + title: `${item.emoji as string} ${item.title as string}`, + subtitle: undefined, + }), + + dragType: 'habit', + acceptsDropFrom: ['task'], + + transformIncoming: { + task: (source) => ({ + title: source.title as string, + emoji: '\ud83d\udcaa', + color: '#6366f1', + }), + }, + + createItem: async (data) => { + const habit = await habitsStore.createHabit({ + title: data.title as string, + emoji: (data.emoji as string) ?? '\u2b50', + color: (data.color as string) ?? '#6366f1', + }); + return habit.id; + }, +}; + +registerEntity(habitsEntity); diff --git a/apps/manacore/apps/web/src/lib/modules/habits/index.ts b/apps/manacore/apps/web/src/lib/modules/habits/index.ts new file mode 100644 index 000000000..22851dd9e --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/habits/index.ts @@ -0,0 +1,30 @@ +/** + * Habits module — barrel exports. + */ + +// ─── Stores ────────────────────────────────────────────── +export { habitsStore } from './stores/habits.svelte'; + +// ─── Queries ───────────────────────────────────────────── +export { + useAllHabits, + useAllHabitLogs, + useHabitLogsForHabit, + toHabit, + toHabitLog, + todayStr, + getLogsForDate, + getCountForDate, + getActiveHabits, + getTodayCounts, + getStreak, + groupLogsByDate, + formatTime, +} from './queries'; + +// ─── Collections ───────────────────────────────────────── +export { habitTable, habitLogTable, HABITS_GUEST_SEED } from './collections'; + +// ─── Types ─────────────────────────────────────────────── +export { HABIT_COLORS, HABIT_EMOJIS } from './types'; +export type { LocalHabit, LocalHabitLog, Habit, HabitLog } from './types'; diff --git a/apps/manacore/apps/web/src/lib/modules/habits/queries.ts b/apps/manacore/apps/web/src/lib/modules/habits/queries.ts new file mode 100644 index 000000000..28f350c17 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/habits/queries.ts @@ -0,0 +1,138 @@ +/** + * Reactive Queries & Pure Helpers for Habits module. + * + * Uses Dexie liveQuery on the unified database. + */ + +import { liveQuery } from 'dexie'; +import { db } from '$lib/data/database'; +import type { LocalHabit, LocalHabitLog, Habit, HabitLog } from './types'; + +// ─── Type Converters ─────────────────────────────────────── + +export function toHabit(local: LocalHabit): Habit { + return { + id: local.id, + title: local.title, + emoji: local.emoji, + color: local.color, + targetPerDay: local.targetPerDay, + order: local.order, + isArchived: local.isArchived, + createdAt: local.createdAt ?? new Date().toISOString(), + updatedAt: local.updatedAt ?? new Date().toISOString(), + }; +} + +export function toHabitLog(local: LocalHabitLog): HabitLog { + return { + id: local.id, + habitId: local.habitId, + timestamp: local.timestamp, + note: local.note, + createdAt: local.createdAt ?? new Date().toISOString(), + }; +} + +// ─── Live Queries ────────────────────────────────────────── + +export function useAllHabits() { + return liveQuery(async () => { + const locals = await db.table('habits').orderBy('order').toArray(); + return locals.filter((h) => !h.deletedAt).map(toHabit); + }); +} + +export function useAllHabitLogs() { + return liveQuery(async () => { + const locals = await db.table('habitLogs').toArray(); + return locals.filter((l) => !l.deletedAt).map(toHabitLog); + }); +} + +export function useHabitLogsForHabit(habitId: string) { + return liveQuery(async () => { + const locals = await db + .table('habitLogs') + .where('habitId') + .equals(habitId) + .toArray(); + return locals + .filter((l) => !l.deletedAt) + .map(toHabitLog) + .sort((a, b) => b.timestamp.localeCompare(a.timestamp)); + }); +} + +// ─── Pure Helpers ────────────────────────────────────────── + +/** Get today's date string (YYYY-MM-DD) */ +export function todayStr(): string { + return new Date().toISOString().split('T')[0]; +} + +/** Filter logs for a specific date */ +export function getLogsForDate(logs: HabitLog[], date: string): HabitLog[] { + return logs.filter((l) => l.timestamp.startsWith(date)); +} + +/** Count logs for a specific habit on a given date */ +export function getCountForDate(logs: HabitLog[], habitId: string, date: string): number { + return logs.filter((l) => l.habitId === habitId && l.timestamp.startsWith(date)).length; +} + +/** Get active (non-archived) habits */ +export function getActiveHabits(habits: Habit[]): Habit[] { + return habits.filter((h) => !h.isArchived).sort((a, b) => a.order - b.order); +} + +/** Get today's count per habit */ +export function getTodayCounts(habits: Habit[], logs: HabitLog[]): Map { + const today = todayStr(); + const counts = new Map(); + for (const h of habits) { + counts.set(h.id, getCountForDate(logs, h.id, today)); + } + return counts; +} + +/** Calculate streak (consecutive days with at least one log) */ +export function getStreak(logs: HabitLog[], habitId: string): number { + const habitLogs = logs.filter((l) => l.habitId === habitId); + if (habitLogs.length === 0) return 0; + + const dates = new Set(habitLogs.map((l) => l.timestamp.split('T')[0])); + let streak = 0; + const d = new Date(); + + while (true) { + const dateStr = d.toISOString().split('T')[0]; + if (dates.has(dateStr)) { + streak++; + d.setDate(d.getDate() - 1); + } else { + break; + } + } + + return streak; +} + +/** Group logs by date (most recent first) */ +export function groupLogsByDate(logs: HabitLog[]): Map { + const groups = new Map(); + const sorted = [...logs].sort((a, b) => b.timestamp.localeCompare(a.timestamp)); + for (const log of sorted) { + const date = log.timestamp.split('T')[0]; + const existing = groups.get(date) || []; + existing.push(log); + groups.set(date, existing); + } + return groups; +} + +/** Format time from ISO string to HH:MM */ +export function formatTime(iso: string): string { + const d = new Date(iso); + return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; +} diff --git a/apps/manacore/apps/web/src/lib/modules/habits/stores/habits.svelte.ts b/apps/manacore/apps/web/src/lib/modules/habits/stores/habits.svelte.ts new file mode 100644 index 000000000..3c24ddffe --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/habits/stores/habits.svelte.ts @@ -0,0 +1,100 @@ +/** + * Habits Store — Mutation-Only Service + * + * All reads are handled by liveQuery hooks in queries.ts. + * This store only provides write operations. + */ + +import { habitTable, habitLogTable } from '../collections'; +import { toHabit } from '../queries'; +import type { LocalHabit, LocalHabitLog } from '../types'; + +export const habitsStore = { + async createHabit(data: { + title: string; + emoji: string; + color: string; + targetPerDay?: number | null; + }) { + const existing = await habitTable.toArray(); + const count = existing.filter((h) => !h.deletedAt).length; + + const newLocal: LocalHabit = { + id: crypto.randomUUID(), + title: data.title, + emoji: data.emoji, + color: data.color, + targetPerDay: data.targetPerDay ?? null, + order: count, + isArchived: false, + }; + + await habitTable.add(newLocal); + return toHabit(newLocal); + }, + + async updateHabit( + id: string, + data: Partial< + Pick + > + ) { + await habitTable.update(id, { + ...data, + updatedAt: new Date().toISOString(), + }); + }, + + async deleteHabit(id: string) { + await habitTable.update(id, { + deletedAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }); + // Also soft-delete all logs for this habit + const logs = await habitLogTable.where('habitId').equals(id).toArray(); + const now = new Date().toISOString(); + for (const log of logs) { + await habitLogTable.update(log.id, { deletedAt: now }); + } + }, + + async logHabit(habitId: string, note?: string) { + const newLog: LocalHabitLog = { + id: crypto.randomUUID(), + habitId, + timestamp: new Date().toISOString(), + note: note ?? null, + }; + + await habitLogTable.add(newLog); + return newLog; + }, + + async deleteLog(logId: string) { + await habitLogTable.update(logId, { + deletedAt: new Date().toISOString(), + }); + }, + + async undoLastLog(habitId: string) { + const logs = await habitLogTable.where('habitId').equals(habitId).toArray(); + const active = logs + .filter((l) => !l.deletedAt) + .sort((a, b) => (b.timestamp ?? '').localeCompare(a.timestamp ?? '')); + if (active.length > 0) { + await habitLogTable.update(active[0].id, { + deletedAt: new Date().toISOString(), + }); + } + }, + + async reorderHabits(habitIds: string[]) { + const now = new Date().toISOString(); + for (let i = 0; i < habitIds.length; i++) { + await habitTable.update(habitIds[i], { + order: i, + updatedAt: now, + }); + } + }, +}; diff --git a/apps/manacore/apps/web/src/lib/modules/habits/types.ts b/apps/manacore/apps/web/src/lib/modules/habits/types.ts new file mode 100644 index 000000000..d3f8e7a24 --- /dev/null +++ b/apps/manacore/apps/web/src/lib/modules/habits/types.ts @@ -0,0 +1,85 @@ +/** + * Habits module types for the unified app. + * + * Habit = a trackable behavior (e.g. Coffee, Cigarette, Workout) + * HabitLog = a single occurrence/tally with timestamp + */ + +import type { BaseRecord } from '@manacore/local-store'; + +// ─── Local Record Types (Dexie) ─────────────────────────── + +export interface LocalHabit extends BaseRecord { + title: string; + emoji: string; + color: string; + targetPerDay: number | null; + order: number; + isArchived: boolean; +} + +export interface LocalHabitLog extends BaseRecord { + habitId: string; + timestamp: string; // ISO string + note: string | null; +} + +// ─── Domain Types ───────────────────────────────────────── + +export interface Habit { + id: string; + title: string; + emoji: string; + color: string; + targetPerDay: number | null; + order: number; + isArchived: boolean; + createdAt: string; + updatedAt: string; +} + +export interface HabitLog { + id: string; + habitId: string; + timestamp: string; + note: string | null; + createdAt: string; +} + +// ─── Constants ──────────────────────────────────────────── + +export const HABIT_COLORS: string[] = [ + '#ef4444', + '#f97316', + '#f59e0b', + '#84cc16', + '#22c55e', + '#14b8a6', + '#06b6d4', + '#3b82f6', + '#6366f1', + '#8b5cf6', + '#a855f7', + '#d946ef', + '#ec4899', + '#f43f5e', +]; + +export const HABIT_EMOJIS: string[] = [ + '\u2615', // coffee + '\ud83d\udeb6', // cigarette / walking + '\ud83c\udfc3', // running + '\ud83e\uddd8', // meditation + '\ud83d\udca7', // water + '\ud83c\udf4e', // apple / healthy food + '\ud83d\udcda', // reading + '\ud83d\udcaa', // workout + '\ud83d\udecc', // sleep + '\ud83c\udfb5', // music + '\ud83d\udc8a', // pill / medicine + '\ud83c\udf7a', // beer + '\ud83c\udf55', // pizza / junk food + '\ud83d\udeb4', // cycling + '\ud83d\udcdd', // journal + '\ud83e\uddfc', // teeth / hygiene +]; diff --git a/apps/manacore/apps/web/src/routes/(app)/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/+page.svelte index 7ac563297..1d7f96216 100644 --- a/apps/manacore/apps/web/src/routes/(app)/+page.svelte +++ b/apps/manacore/apps/web/src/routes/(app)/+page.svelte @@ -41,6 +41,7 @@ { appId: 'todo', minimized: false }, { appId: 'calendar', minimized: false }, { appId: 'contacts', minimized: false }, + { appId: 'habits', minimized: false }, ], }); @@ -56,6 +57,7 @@ { appId: 'todo', minimized: false }, { appId: 'calendar', minimized: false }, { appId: 'contacts', minimized: false }, + { appId: 'habits', minimized: false }, ]); // Load persisted state once on mount (not reactive — avoids loop with persistState) diff --git a/apps/manacore/apps/web/src/routes/(app)/habits/+layout.svelte b/apps/manacore/apps/web/src/routes/(app)/habits/+layout.svelte new file mode 100644 index 000000000..78eb25889 --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/habits/+layout.svelte @@ -0,0 +1,15 @@ + + +{@render children()} diff --git a/apps/manacore/apps/web/src/routes/(app)/habits/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/habits/+page.svelte new file mode 100644 index 000000000..71a17cfe4 --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/habits/+page.svelte @@ -0,0 +1,122 @@ + + + + Habits - ManaCore + + +
+
+
+

Habits

+ {#if isLoaded} +
+ {habits.filter((h) => !h.isArchived).length} Habits + {todayLogs.length} Einträge heute +
+ {/if} +
+
+ + {#if isLoaded} +
+ +
+ + {#if todayLogs.length > 0} +
+

Heute

+ +
+ {/if} + {:else} +
Laden...
+ {/if} +
+ + diff --git a/apps/manacore/apps/web/src/routes/(app)/habits/[id]/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/habits/[id]/+page.svelte new file mode 100644 index 000000000..6c6eeed25 --- /dev/null +++ b/apps/manacore/apps/web/src/routes/(app)/habits/[id]/+page.svelte @@ -0,0 +1,76 @@ + + + + {habit ? `${habit.emoji} ${habit.title}` : 'Habit'} - ManaCore + + +
+ {#if habit} + + {:else if habits.length > 0} +
+

Habit nicht gefunden.

+ +
+ {:else} +
Laden...
+ {/if} +
+ + diff --git a/packages/shared-branding/src/app-icons.ts b/packages/shared-branding/src/app-icons.ts index 8a180fdc1..00a7bd5e5 100644 --- a/packages/shared-branding/src/app-icons.ts +++ b/packages/shared-branding/src/app-icons.ts @@ -131,6 +131,9 @@ export const APP_ICONS = { skilltree: svgToDataUrl( `` ), + habits: svgToDataUrl( + `` + ), arcade: svgToDataUrl( `` ), diff --git a/packages/shared-branding/src/mana-apps.ts b/packages/shared-branding/src/mana-apps.ts index b9c1e058e..6ef92c82a 100644 --- a/packages/shared-branding/src/mana-apps.ts +++ b/packages/shared-branding/src/mana-apps.ts @@ -581,6 +581,23 @@ export const MANA_APPS: ManaApp[] = [ status: 'beta', requiredTier: 'alpha', }, + { + id: 'habits', + name: 'Habits', + description: { + de: 'Gewohnheiten tracken', + en: 'Habit Tracking', + }, + longDescription: { + de: 'Schnelles Tally-Tracking für Gewohnheiten wie Kaffee, Zigaretten, Wasser — ein Tap pro Eintrag mit Tagesstatistiken und Streaks.', + en: 'Quick tally tracking for habits like coffee, cigarettes, water — one tap per entry with daily stats and streaks.', + }, + icon: APP_ICONS.habits, + color: '#8b5cf6', + comingSoon: false, + status: 'development', + requiredTier: 'founder', + }, { id: 'arcade', name: 'Arcade', @@ -709,6 +726,7 @@ export const APP_URLS: Record = { moodlit: { dev: 'http://localhost:5173/moodlit', prod: 'https://mana.how/moodlit' }, memoro: { dev: 'http://localhost:5173/memoro', prod: 'https://mana.how/memoro' }, guides: { dev: 'http://localhost:5173/guides', prod: 'https://mana.how/guides' }, + habits: { dev: 'http://localhost:5173/habits', prod: 'https://mana.how/habits' }, wisekeep: { dev: 'http://localhost:5173/wisekeep', prod: 'https://mana.how/wisekeep' }, news: { dev: 'http://localhost:5173/news', prod: 'https://mana.how/news' }, mail: { dev: 'http://localhost:5173/mail', prod: 'https://mana.how/mail' }, diff --git a/packages/shared-ui/src/dnd/types.ts b/packages/shared-ui/src/dnd/types.ts index 2e7b1b2d0..1571e4557 100644 --- a/packages/shared-ui/src/dnd/types.ts +++ b/packages/shared-ui/src/dnd/types.ts @@ -11,7 +11,16 @@ // ── Drag types ────────────────────────────────────────────── -export type DragType = 'tag' | 'task' | 'card' | 'photo' | 'file' | 'event' | 'link' | 'contact'; +export type DragType = + | 'tag' + | 'task' + | 'card' + | 'photo' + | 'file' + | 'event' + | 'link' + | 'contact' + | 'habit'; export interface DragPayload> { type: DragType;