@autumnsgrove/groveengine 0.4.12 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +5 -3
  2. package/dist/components/OnboardingChecklist.svelte +118 -0
  3. package/dist/components/OnboardingChecklist.svelte.d.ts +14 -0
  4. package/dist/components/quota/QuotaWarning.svelte +125 -0
  5. package/dist/components/quota/QuotaWarning.svelte.d.ts +16 -0
  6. package/dist/components/quota/QuotaWidget.svelte +120 -0
  7. package/dist/components/quota/QuotaWidget.svelte.d.ts +15 -0
  8. package/dist/components/quota/UpgradePrompt.svelte +288 -0
  9. package/dist/components/quota/UpgradePrompt.svelte.d.ts +13 -0
  10. package/dist/components/quota/index.d.ts +8 -0
  11. package/dist/components/quota/index.js +8 -0
  12. package/dist/groveauth/client.d.ts +143 -0
  13. package/dist/groveauth/client.js +502 -0
  14. package/dist/groveauth/colors.d.ts +35 -0
  15. package/dist/groveauth/colors.js +91 -0
  16. package/dist/groveauth/index.d.ts +34 -0
  17. package/dist/groveauth/index.js +35 -0
  18. package/dist/groveauth/limits.d.ts +70 -0
  19. package/dist/groveauth/limits.js +202 -0
  20. package/dist/groveauth/rate-limit.d.ts +95 -0
  21. package/dist/groveauth/rate-limit.js +172 -0
  22. package/dist/groveauth/types.d.ts +139 -0
  23. package/dist/groveauth/types.js +61 -0
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.js +4 -0
  26. package/dist/payments/types.d.ts +7 -2
  27. package/dist/server/services/__mocks__/cloudflare.d.ts +54 -0
  28. package/dist/server/services/__mocks__/cloudflare.js +470 -0
  29. package/dist/server/services/cache.d.ts +170 -0
  30. package/dist/server/services/cache.js +335 -0
  31. package/dist/server/services/database.d.ts +236 -0
  32. package/dist/server/services/database.js +450 -0
  33. package/dist/server/services/index.d.ts +34 -0
  34. package/dist/server/services/index.js +77 -0
  35. package/dist/server/services/storage.d.ts +221 -0
  36. package/dist/server/services/storage.js +485 -0
  37. package/package.json +11 -1
  38. package/static/fonts/Calistoga-Regular.ttf +1438 -0
  39. package/static/fonts/Caveat-Regular.ttf +0 -0
  40. package/static/fonts/EBGaramond-Regular.ttf +0 -0
  41. package/static/fonts/Fraunces-Regular.ttf +0 -0
  42. package/static/fonts/InstrumentSans-Regular.ttf +0 -0
  43. package/static/fonts/Lora-Regular.ttf +0 -0
  44. package/static/fonts/Luciole-Regular.ttf +1438 -0
  45. package/static/fonts/Manrope-Regular.ttf +0 -0
  46. package/static/fonts/Merriweather-Regular.ttf +1439 -0
  47. package/static/fonts/Nunito-Regular.ttf +0 -0
  48. package/static/fonts/PlusJakartaSans-Regular.ttf +0 -0
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
- # @grove/engine
1
+ # Lattice (@autumnsgrove/groveengine)
2
2
 
3
- Multi-tenant blog engine for the Grove Platform. Each Grove site runs as its own Cloudflare Worker, powered by this engine.
3
+ > **Internal codename:** GroveEngine
4
+
5
+ Multi-tenant blog engine for the Grove Platform. Each Grove site runs as its own Cloudflare Worker, powered by Lattice. A lattice is the framework that supports growth—vines climb it, gardens are built around it.
4
6
 
5
7
  ## Features
6
8
 
@@ -106,7 +108,7 @@ RESEND_API_KEY=re_xxxxx
106
108
 
107
109
  ## Fonts
108
110
 
109
- GroveEngine includes self-hosted accessibility-focused fonts in `static/fonts/`. After installing the package, copy the fonts to your project's static directory:
111
+ Lattice includes self-hosted accessibility-focused fonts in `static/fonts/`. After installing the package, copy the fonts to your project's static directory:
110
112
 
111
113
  ```bash
112
114
  # Copy fonts from node_modules to your static folder
@@ -0,0 +1,118 @@
1
+ <script lang="ts">
2
+ import { Check, Circle, X, Sparkles } from 'lucide-svelte';
3
+
4
+ interface ChecklistItem {
5
+ id: string;
6
+ label: string;
7
+ completed: boolean;
8
+ href?: string;
9
+ }
10
+
11
+ interface Props {
12
+ items: ChecklistItem[];
13
+ onDismiss?: () => void;
14
+ class?: string;
15
+ }
16
+
17
+ let { items, onDismiss, class: className = '' }: Props = $props();
18
+
19
+ const completedCount = $derived(items.filter((i) => i.completed).length);
20
+ const allCompleted = $derived(completedCount === items.length);
21
+ const progress = $derived((completedCount / items.length) * 100);
22
+ </script>
23
+
24
+ <div class="bg-surface-elevated border border-default rounded-lg p-4 {className}">
25
+ <!-- Header -->
26
+ <div class="flex items-center justify-between mb-4">
27
+ <div class="flex items-center gap-2">
28
+ <Sparkles size={18} class="text-primary" />
29
+ <h3 class="font-medium text-foreground text-sm">Getting Started</h3>
30
+ </div>
31
+ {#if onDismiss}
32
+ <button
33
+ onclick={onDismiss}
34
+ class="text-foreground-subtle hover:text-foreground transition-colors"
35
+ title="Dismiss checklist"
36
+ >
37
+ <X size={16} />
38
+ </button>
39
+ {/if}
40
+ </div>
41
+
42
+ <!-- Progress bar -->
43
+ <div class="mb-4">
44
+ <div class="h-1.5 bg-surface rounded-full overflow-hidden">
45
+ <div
46
+ class="h-full bg-primary transition-all duration-500"
47
+ style="width: {progress}%"
48
+ ></div>
49
+ </div>
50
+ <p class="text-xs text-foreground-subtle mt-1">
51
+ {completedCount} of {items.length} complete
52
+ </p>
53
+ </div>
54
+
55
+ <!-- Checklist items -->
56
+ <ul class="space-y-2">
57
+ {#each items as item}
58
+ <li>
59
+ {#if item.href && !item.completed}
60
+ <a
61
+ href={item.href}
62
+ class="flex items-center gap-3 p-2 rounded-md hover:bg-surface transition-colors group"
63
+ >
64
+ <div
65
+ class="w-5 h-5 rounded-full border-2 flex items-center justify-center flex-shrink-0 transition-colors"
66
+ class:border-primary={item.completed}
67
+ class:bg-primary={item.completed}
68
+ class:border-default={!item.completed}
69
+ class:group-hover:border-primary={!item.completed}
70
+ >
71
+ {#if item.completed}
72
+ <Check size={12} class="text-white" />
73
+ {/if}
74
+ </div>
75
+ <span
76
+ class="text-sm transition-colors"
77
+ class:text-foreground-subtle={item.completed}
78
+ class:line-through={item.completed}
79
+ class:text-foreground={!item.completed}
80
+ >
81
+ {item.label}
82
+ </span>
83
+ </a>
84
+ {:else}
85
+ <div class="flex items-center gap-3 p-2">
86
+ <div
87
+ class="w-5 h-5 rounded-full border-2 flex items-center justify-center flex-shrink-0"
88
+ class:border-primary={item.completed}
89
+ class:bg-primary={item.completed}
90
+ class:border-default={!item.completed}
91
+ >
92
+ {#if item.completed}
93
+ <Check size={12} class="text-white" />
94
+ {/if}
95
+ </div>
96
+ <span
97
+ class="text-sm"
98
+ class:text-foreground-subtle={item.completed}
99
+ class:line-through={item.completed}
100
+ class:text-foreground={!item.completed}
101
+ >
102
+ {item.label}
103
+ </span>
104
+ </div>
105
+ {/if}
106
+ </li>
107
+ {/each}
108
+ </ul>
109
+
110
+ <!-- Completion celebration -->
111
+ {#if allCompleted}
112
+ <div class="mt-4 p-3 bg-accent rounded-md text-center">
113
+ <p class="text-sm text-foreground">
114
+ 🎉 All done! You're a Grove pro now.
115
+ </p>
116
+ </div>
117
+ {/if}
118
+ </div>
@@ -0,0 +1,14 @@
1
+ interface ChecklistItem {
2
+ id: string;
3
+ label: string;
4
+ completed: boolean;
5
+ href?: string;
6
+ }
7
+ interface Props {
8
+ items: ChecklistItem[];
9
+ onDismiss?: () => void;
10
+ class?: string;
11
+ }
12
+ declare const OnboardingChecklist: import("svelte").Component<Props, {}, "">;
13
+ type OnboardingChecklist = ReturnType<typeof OnboardingChecklist>;
14
+ export default OnboardingChecklist;
@@ -0,0 +1,125 @@
1
+ <script lang="ts">
2
+ /**
3
+ * QuotaWarning - Warning banner for post limit status
4
+ *
5
+ * Shows contextual warnings when users are near or at their limit.
6
+ * Can be placed on the post editor or dashboard.
7
+ */
8
+
9
+ import type { PreSubmitCheckResult, AlertVariant } from '../../groveauth/index.js';
10
+ import { ALERT_VARIANTS } from '../../groveauth/index.js';
11
+
12
+ interface Props {
13
+ check: PreSubmitCheckResult;
14
+ upgradeUrl?: string;
15
+ onDismiss?: () => void;
16
+ showDismiss?: boolean;
17
+ }
18
+
19
+ let {
20
+ check,
21
+ upgradeUrl = '/upgrade',
22
+ onDismiss,
23
+ showDismiss = true,
24
+ }: Props = $props();
25
+
26
+ let dismissed = $state(false);
27
+
28
+ function handleDismiss() {
29
+ dismissed = true;
30
+ onDismiss?.();
31
+ }
32
+
33
+ // Defensive check for malformed data
34
+ const isValidCheck = $derived(
35
+ check &&
36
+ typeof check === 'object' &&
37
+ check.status &&
38
+ typeof check.status === 'object'
39
+ );
40
+
41
+ // Determine variant based on check result
42
+ const variant = $derived<AlertVariant>(
43
+ !isValidCheck ? 'info' :
44
+ check.upgradeRequired ? 'error' :
45
+ check.status.is_in_grace_period ? 'warning' :
46
+ check.status.is_at_limit ? 'warning' :
47
+ 'info'
48
+ );
49
+
50
+ // Get variant classes from shared utility
51
+ const variantClasses = $derived(ALERT_VARIANTS[variant]);
52
+ </script>
53
+
54
+ {#if isValidCheck && check.showWarning && !dismissed}
55
+ <div class="rounded-lg border p-4 {variantClasses.container}">
56
+ <div class="flex items-start gap-3">
57
+ <!-- Icon -->
58
+ <div class="flex-shrink-0">
59
+ {#if variant === 'error'}
60
+ <svg class="w-5 h-5 {variantClasses.icon}" fill="currentColor" viewBox="0 0 20 20">
61
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
62
+ </svg>
63
+ {:else if variant === 'warning'}
64
+ <svg class="w-5 h-5 {variantClasses.icon}" fill="currentColor" viewBox="0 0 20 20">
65
+ <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
66
+ </svg>
67
+ {:else}
68
+ <svg class="w-5 h-5 {variantClasses.icon}" fill="currentColor" viewBox="0 0 20 20">
69
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
70
+ </svg>
71
+ {/if}
72
+ </div>
73
+
74
+ <!-- Content -->
75
+ <div class="flex-1">
76
+ <h3 class="text-sm font-medium {variantClasses.title}">
77
+ {#if check.upgradeRequired}
78
+ Upgrade Required
79
+ {:else if check.status.is_in_grace_period}
80
+ Grace Period Active
81
+ {:else if check.status.is_at_limit}
82
+ Post Limit Reached
83
+ {:else}
84
+ Approaching Limit
85
+ {/if}
86
+ </h3>
87
+
88
+ {#if check.warningMessage}
89
+ <p class="mt-1 text-sm {variantClasses.text}">
90
+ {check.warningMessage}
91
+ </p>
92
+ {/if}
93
+
94
+ <!-- Actions -->
95
+ <div class="mt-3 flex items-center gap-3">
96
+ <a
97
+ href={upgradeUrl}
98
+ class="inline-flex items-center px-3 py-1.5 text-sm font-medium rounded-md {variantClasses.button}"
99
+ >
100
+ {check.upgradeRequired ? 'Upgrade Now' : 'View Plans'}
101
+ </a>
102
+
103
+ {#if !check.upgradeRequired}
104
+ <span class="text-sm {variantClasses.text}">
105
+ {check.status.posts_remaining !== null ? `${check.status.posts_remaining} posts remaining` : ''}
106
+ </span>
107
+ {/if}
108
+ </div>
109
+ </div>
110
+
111
+ <!-- Dismiss button -->
112
+ {#if showDismiss && !check.upgradeRequired}
113
+ <button
114
+ onclick={handleDismiss}
115
+ class="flex-shrink-0 {variantClasses.text} hover:opacity-75"
116
+ aria-label="Dismiss"
117
+ >
118
+ <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
119
+ <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
120
+ </svg>
121
+ </button>
122
+ {/if}
123
+ </div>
124
+ </div>
125
+ {/if}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * QuotaWarning - Warning banner for post limit status
3
+ *
4
+ * Shows contextual warnings when users are near or at their limit.
5
+ * Can be placed on the post editor or dashboard.
6
+ */
7
+ import type { PreSubmitCheckResult } from '../../groveauth/index.js';
8
+ interface Props {
9
+ check: PreSubmitCheckResult;
10
+ upgradeUrl?: string;
11
+ onDismiss?: () => void;
12
+ showDismiss?: boolean;
13
+ }
14
+ declare const QuotaWarning: import("svelte").Component<Props, {}, "">;
15
+ type QuotaWarning = ReturnType<typeof QuotaWarning>;
16
+ export default QuotaWarning;
@@ -0,0 +1,120 @@
1
+ <script lang="ts">
2
+ /**
3
+ * QuotaWidget - Displays post usage quota
4
+ *
5
+ * Shows current post count, limit, and visual progress bar.
6
+ * Includes upgrade prompts when near or at limit.
7
+ */
8
+
9
+ import type { QuotaWidgetData } from '../../groveauth/index.js';
10
+ import { STATUS_COLORS } from '../../groveauth/index.js';
11
+
12
+ interface Props {
13
+ data: QuotaWidgetData;
14
+ upgradeUrl?: string;
15
+ compact?: boolean;
16
+ }
17
+
18
+ let { data, upgradeUrl = '/upgrade', compact = false }: Props = $props();
19
+
20
+ // Defensive check for malformed data
21
+ const isValidData = $derived(
22
+ data &&
23
+ typeof data === 'object' &&
24
+ typeof data.count === 'number' &&
25
+ typeof data.color === 'string'
26
+ );
27
+
28
+ // Default fallback data for when data is invalid
29
+ const safeData = $derived(isValidData ? data : {
30
+ count: 0,
31
+ limit: null,
32
+ percentage: null,
33
+ remaining: null,
34
+ color: 'gray' as const,
35
+ statusText: 'Loading...',
36
+ description: 'Unable to load quota information',
37
+ showUpgrade: false,
38
+ tierName: 'Unknown',
39
+ canPost: true,
40
+ });
41
+
42
+ // Get color classes from shared utility
43
+ const colorClasses = $derived(STATUS_COLORS[safeData.color]);
44
+ </script>
45
+
46
+ {#if compact}
47
+ <!-- Compact mode: just the numbers -->
48
+ <div class="flex items-center gap-2 text-sm">
49
+ <span class="font-medium">
50
+ {safeData.count}{#if safeData.limit !== null}<span class="text-gray-400">/{safeData.limit}</span>{/if}
51
+ </span>
52
+ <span class="px-1.5 py-0.5 text-xs rounded {colorClasses.badge}">
53
+ {safeData.statusText}
54
+ </span>
55
+ </div>
56
+ {:else}
57
+ <!-- Full widget -->
58
+ <div class="rounded-lg border border-gray-200 dark:border-gray-700 p-4 {colorClasses.bg}">
59
+ <div class="flex justify-between items-center mb-2">
60
+ <h4 class="font-semibold text-gray-900 dark:text-gray-100">Post Usage</h4>
61
+ <span class="px-2 py-1 text-xs font-medium rounded-full {colorClasses.badge}">
62
+ {safeData.statusText}
63
+ </span>
64
+ </div>
65
+
66
+ <!-- Count display -->
67
+ <div class="text-2xl font-bold mb-2 text-gray-900 dark:text-gray-100">
68
+ {safeData.count}{#if safeData.limit !== null}<span class="text-gray-400 dark:text-gray-500">/{safeData.limit}</span>{/if}
69
+ </div>
70
+
71
+ <!-- Progress bar -->
72
+ {#if safeData.limit !== null && safeData.percentage !== null}
73
+ <div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2 mb-2">
74
+ <div
75
+ class="h-full rounded-full transition-all duration-300 {colorClasses.fill}"
76
+ style="width: {Math.min(safeData.percentage, 100)}%"
77
+ ></div>
78
+ </div>
79
+ <p class="text-sm {colorClasses.text}">{safeData.percentage.toFixed(1)}% used</p>
80
+ {:else}
81
+ <p class="text-sm text-gray-500 dark:text-gray-400">Unlimited posts with {safeData.tierName} plan</p>
82
+ {/if}
83
+
84
+ <!-- Upgrade prompt -->
85
+ {#if safeData.showUpgrade}
86
+ <div class="mt-4 p-3 bg-blue-50 dark:bg-blue-900/30 rounded-lg">
87
+ <p class="text-sm text-blue-800 dark:text-blue-200 mb-2">
88
+ Upgrade for more posts
89
+ </p>
90
+ <a
91
+ href={upgradeUrl}
92
+ class="inline-flex items-center text-sm font-medium text-blue-600 dark:text-blue-400 hover:underline"
93
+ >
94
+ View Plans
95
+ <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
96
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
97
+ </svg>
98
+ </a>
99
+ </div>
100
+ {/if}
101
+
102
+ <!-- Cannot post warning -->
103
+ {#if !safeData.canPost}
104
+ <div class="mt-4 p-3 bg-red-50 dark:bg-red-900/30 rounded-lg border border-red-200 dark:border-red-800">
105
+ <p class="text-sm text-red-800 dark:text-red-200 font-medium">
106
+ You cannot create new posts until you upgrade or delete existing posts.
107
+ </p>
108
+ </div>
109
+ {/if}
110
+
111
+ <!-- Error state for invalid data -->
112
+ {#if !isValidData}
113
+ <div class="mt-4 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
114
+ <p class="text-sm text-gray-600 dark:text-gray-400">
115
+ Unable to load quota information. Please refresh the page.
116
+ </p>
117
+ </div>
118
+ {/if}
119
+ </div>
120
+ {/if}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * QuotaWidget - Displays post usage quota
3
+ *
4
+ * Shows current post count, limit, and visual progress bar.
5
+ * Includes upgrade prompts when near or at limit.
6
+ */
7
+ import type { QuotaWidgetData } from '../../groveauth/index.js';
8
+ interface Props {
9
+ data: QuotaWidgetData;
10
+ upgradeUrl?: string;
11
+ compact?: boolean;
12
+ }
13
+ declare const QuotaWidget: import("svelte").Component<Props, {}, "">;
14
+ type QuotaWidget = ReturnType<typeof QuotaWidget>;
15
+ export default QuotaWidget;