@hogsend/cli 0.2.2 → 0.2.3
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.
- package/package.json +2 -2
- package/skills/hogsend-authoring-buckets/SKILL.md +202 -23
- package/skills/hogsend-authoring-buckets/references/bucket-id-aliases.md +74 -59
- package/skills/hogsend-authoring-buckets/references/bucket-meta.md +19 -4
- package/skills/hogsend-authoring-buckets/references/buckets-vs-journeys.md +52 -8
- package/skills/hogsend-authoring-buckets/references/register-a-bucket.md +58 -24
- package/studio/assets/index-BNDE5JtQ.css +1 -0
- package/studio/assets/{index-B49mArEh.js → index-r9qr4mus.js} +11 -11
- package/studio/index.html +2 -2
- package/studio/assets/index-CycKZchB.css +0 -1
|
@@ -7,35 +7,47 @@ bucket is half-alive.
|
|
|
7
7
|
|
|
8
8
|
## 1. Export from `src/buckets/index.ts`
|
|
9
9
|
|
|
10
|
-
The barrel exports the `buckets
|
|
11
|
-
|
|
10
|
+
The barrel exports the `buckets` array — this is the single list both factories
|
|
11
|
+
consume. Let the array INFER its element types; do NOT annotate it
|
|
12
|
+
`DefinedBucket[]`. The annotation re-widens each bucket's `Id` literal back to
|
|
13
|
+
`string`, which would erase the literal types on `bucket.entered` / `bucket.left`
|
|
14
|
+
(see bucket-id-aliases).
|
|
12
15
|
|
|
13
16
|
```ts
|
|
14
17
|
// src/buckets/index.ts
|
|
15
|
-
import type { DefinedBucket } from "@hogsend/engine";
|
|
16
18
|
import { powerUsers } from "./power-users.js";
|
|
17
19
|
import { wentDormant } from "./went-dormant.js";
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* All defined buckets for this app. Passed to createHogsendClient({ buckets })
|
|
21
23
|
* and createWorker({ buckets }). Edit freely — this is your content.
|
|
24
|
+
* No `DefinedBucket[]` annotation — let the literal ids survive for typed refs.
|
|
22
25
|
*/
|
|
23
|
-
export const buckets
|
|
26
|
+
export const buckets = [powerUsers, wentDormant];
|
|
24
27
|
|
|
25
|
-
// Re-export individual buckets for direct reference (tests, custom wiring
|
|
28
|
+
// Re-export individual buckets for direct reference (tests, custom wiring,
|
|
29
|
+
// and binding journeys to their typed `.entered` / `.left` refs).
|
|
26
30
|
export { powerUsers, wentDormant };
|
|
27
31
|
```
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
`
|
|
31
|
-
|
|
33
|
+
`createHogsendClient` / `createWorker` accept the base `DefinedBucket[]`, and a
|
|
34
|
+
`DefinedBucket<Id>` is assignable to `DefinedBucket`, so the inferred literal
|
|
35
|
+
array still type-checks at both factories — dropping the annotation is a pure
|
|
36
|
+
type-ergonomics win, never a wiring requirement.
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
That's the whole registration step. There is no separate `BucketId` union to
|
|
39
|
+
update anymore (the typed refs replace it — see bucket-id-aliases), and any
|
|
40
|
+
`.on()` reactions you attached ship automatically on the bucket (see below). You
|
|
41
|
+
do NOT register reactions separately.
|
|
42
|
+
|
|
43
|
+
## 2. Thread into `createHogsendClient` (registry + real-time + reconcile + reactions)
|
|
34
44
|
|
|
35
45
|
In `src/index.ts` (the HTTP entry point), the client receives `buckets`. This
|
|
36
46
|
builds the `BucketRegistry`, installs it as the process singleton (so the
|
|
37
|
-
real-time ingest path and the reconcile cron can resolve it),
|
|
38
|
-
|
|
47
|
+
real-time ingest path and the reconcile cron can resolve it), validates every
|
|
48
|
+
`meta` via `bucketMetaSchema.parse()`, and registers each bucket's `.on()`
|
|
49
|
+
reactions into the journey registry (so the admin/Studio feed and the dwell cron
|
|
50
|
+
can resolve them).
|
|
39
51
|
|
|
40
52
|
```ts
|
|
41
53
|
// src/index.ts
|
|
@@ -52,12 +64,13 @@ const client = createHogsendClient({ journeys, buckets, email: { templates } });
|
|
|
52
64
|
const app = createApp(client, { webhookSources });
|
|
53
65
|
```
|
|
54
66
|
|
|
55
|
-
## 3. Thread into `createWorker` (fast-expiry timer + boot backfill)
|
|
67
|
+
## 3. Thread into `createWorker` (reaction tasks + fast-expiry timer + boot backfill)
|
|
56
68
|
|
|
57
69
|
In `src/worker.ts` (the task-execution entry point), BOTH the client AND the
|
|
58
70
|
worker get `buckets`. The client call here installs the registry for the worker
|
|
59
|
-
process; the `createWorker({ buckets })` call registers the
|
|
60
|
-
|
|
71
|
+
process; the `createWorker({ buckets })` call registers the durable tasks every
|
|
72
|
+
bucket owns — its `.on()` reaction tasks, plus the per-user fast-expiry timer for
|
|
73
|
+
any bucket with `fastExpiry: true`.
|
|
61
74
|
|
|
62
75
|
```ts
|
|
63
76
|
// src/worker.ts
|
|
@@ -76,7 +89,7 @@ async function main() {
|
|
|
76
89
|
const worker = createWorker({
|
|
77
90
|
container: client,
|
|
78
91
|
journeys,
|
|
79
|
-
buckets, // ← registers fastExpiry timer
|
|
92
|
+
buckets, // ← registers reaction tasks + fastExpiry timer(s)
|
|
80
93
|
extraWorkflows,
|
|
81
94
|
});
|
|
82
95
|
|
|
@@ -85,17 +98,33 @@ async function main() {
|
|
|
85
98
|
}
|
|
86
99
|
```
|
|
87
100
|
|
|
101
|
+
## Reactions ship with the bucket (no separate registration)
|
|
102
|
+
|
|
103
|
+
Every `bucket.on("enter" | "leave" | "dwell", ...)` call pushes a generated
|
|
104
|
+
durable journey onto `bucket.reactions`. You do NOT add reactions to the
|
|
105
|
+
`journeys` array, and you do NOT register them anywhere — passing `buckets` to
|
|
106
|
+
both factories is enough:
|
|
107
|
+
|
|
108
|
+
- the client registers each reaction's meta into the journey registry, and
|
|
109
|
+
- the worker registers each reaction's task.
|
|
110
|
+
|
|
111
|
+
Crucially, reactions are gated by **`ENABLED_BUCKETS`, NOT `ENABLED_JOURNEYS`**.
|
|
112
|
+
Their generated ids (`bucket-<id>-on-enter`, etc.) never appear in a consumer's
|
|
113
|
+
`ENABLED_JOURNEYS` csv, so they are selected with their owning bucket and are
|
|
114
|
+
absent whenever the bucket is disabled. (See the dwell/reactions section of the
|
|
115
|
+
main SKILL for what the reactions DO.)
|
|
116
|
+
|
|
88
117
|
## What each side gives you
|
|
89
118
|
|
|
90
119
|
| Wiring | What it enables |
|
|
91
120
|
|--------|-----------------|
|
|
92
|
-
| `createHogsendClient({ buckets })` | Builds + installs the `BucketRegistry` singleton; validates every `meta`; powers the real-time join/leave eval inside `ingestEvent`; lets the reconcile cron resolve enabled buckets. Required in BOTH `index.ts` and `worker.ts`. |
|
|
93
|
-
| `createWorker({ buckets })` | Registers the
|
|
121
|
+
| `createHogsendClient({ buckets })` | Builds + installs the `BucketRegistry` singleton; validates every `meta`; registers each bucket's reaction metas into the journey registry (bucket-gated); powers the real-time join/leave eval inside `ingestEvent`; lets the reconcile cron resolve enabled buckets. Required in BOTH `index.ts` and `worker.ts`. |
|
|
122
|
+
| `createWorker({ buckets })` | Registers each bucket's reaction tasks AND the shared fast-expiry durable timer task (the latter only if some enabled bucket has `fastExpiry: true`). Triggers the boot-time backfill / criteria-change re-eval. |
|
|
94
123
|
|
|
95
|
-
If you ONLY wire the client: time-based
|
|
96
|
-
worker tasks), and a new bucket is never backfilled. If you
|
|
97
|
-
(client `buckets` empty): the registry is empty, so the
|
|
98
|
-
nothing.
|
|
124
|
+
If you ONLY wire the client: reaction tasks, time-based, fast-expiry, and dwell
|
|
125
|
+
fires never run (no worker tasks), and a new bucket is never backfilled. If you
|
|
126
|
+
ONLY wire the worker (client `buckets` empty): the registry is empty, so the
|
|
127
|
+
worker's tasks resolve nothing.
|
|
99
128
|
|
|
100
129
|
## The reconcile cron (engine-owned — you don't register it)
|
|
101
130
|
|
|
@@ -106,12 +135,15 @@ the worker's base workflows — you do NOT add them to `extraWorkflows`. The cro
|
|
|
106
135
|
overrunning sweep queues, never cancels);
|
|
107
136
|
- sweeps every enabled time-based / `maxDwell` dynamic bucket and emits
|
|
108
137
|
`bucket:left` (and absence `bucket:entered`) for members the clock moved;
|
|
138
|
+
- runs the `dwell` pass for any bucket with a `dwell` reaction (firing
|
|
139
|
+
`dwell` over the continuously-resident population — see the main SKILL);
|
|
109
140
|
- is the authoritative backstop even when `fastExpiry` is on.
|
|
110
141
|
|
|
111
142
|
The boot backfill (`bucketBackfillTask`, kicked off by the worker on start):
|
|
112
143
|
|
|
113
144
|
- on a NEW bucket id → materializes the full member set from history WITHOUT
|
|
114
|
-
emitting `bucket:entered` (no historical blast into live journeys)
|
|
145
|
+
emitting `bucket:entered` (no historical blast into live journeys), and derives
|
|
146
|
+
the historical `dwellAnchorAt` so `dwell` can fire for the existing population;
|
|
115
147
|
- on a CHANGED `criteria` (detected via a stored hash diff) → re-evaluates: joins
|
|
116
148
|
new matchers silently, and emits `bucket:left` for members who no longer match.
|
|
117
149
|
|
|
@@ -120,9 +152,11 @@ automatically reconciles existing memberships on boot. You don't run a migration
|
|
|
120
152
|
|
|
121
153
|
## Enabling / disabling at load time
|
|
122
154
|
|
|
123
|
-
- `meta.enabled: false` keeps a bucket out of the registry
|
|
155
|
+
- `meta.enabled: false` keeps a bucket (and its reactions) out of the registry
|
|
156
|
+
entirely.
|
|
124
157
|
- The `ENABLED_BUCKETS` env var (comma-separated ids, or `*` for all) filters
|
|
125
|
-
which buckets load, mirroring `ENABLED_JOURNEYS`.
|
|
158
|
+
which buckets load, mirroring `ENABLED_JOURNEYS`. It gates the bucket AND its
|
|
159
|
+
reactions. You can override per-call via
|
|
126
160
|
`createHogsendClient({ enabledBuckets })` / `createWorker({ enabledBuckets })`.
|
|
127
161
|
|
|
128
162
|
To verify a bucket is live on a running instance (membership counts, transition
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--background: 0 0% 100%;--foreground: 240 10% 3.9%;--card: 0 0% 100%;--card-foreground: 240 10% 3.9%;--primary: 240 5.9% 10%;--primary-foreground: 0 0% 98%;--secondary: 240 4.8% 95.9%;--secondary-foreground: 240 5.9% 10%;--muted: 240 4.8% 95.9%;--muted-foreground: 240 3.8% 46.1%;--accent: 240 4.8% 95.9%;--accent-foreground: 240 5.9% 10%;--destructive: 0 84.2% 60.2%;--destructive-foreground: 0 0% 98%;--border: 240 5.9% 90%;--input: 240 5.9% 90%;--ring: 240 5.9% 10%;--radius: .5rem}*{border-color:hsl(var(--border))}body{background-color:hsl(var(--background));color:hsl(var(--foreground))}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.inset-y-0{top:0;bottom:0}.bottom-4{bottom:1rem}.left-3{left:.75rem}.right-0{right:0}.right-2{right:.5rem}.right-4{right:1rem}.top-1\/2{top:50%}.z-10{z-index:10}.z-50{z-index:50}.z-\[60\]{z-index:60}.-mr-2{margin-right:-.5rem}.-mt-2{margin-top:-.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-1{margin-left:.25rem}.ml-1\.5{margin-left:.375rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.h-10{height:2.5rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-2{height:.5rem}.h-20{height:5rem}.h-24{height:6rem}.h-28{height:7rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-40{height:10rem}.h-48{height:12rem}.h-5{height:1.25rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-96{height:24rem}.h-\[600px\]{height:600px}.h-full{height:100%}.max-h-48{max-height:12rem}.min-h-\[140px\]{min-height:140px}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-60{width:15rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0px}.min-w-\[2px\]{min-width:2px}.max-w-2xl{max-width:42rem}.max-w-lg{max-width:32rem}.max-w-sm{max-width:24rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.caption-bottom{caption-side:bottom}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(0px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px * var(--tw-space-y-reverse))}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-destructive\/40{border-color:hsl(var(--destructive) / .4)}.border-emerald-500\/40{border-color:#10b98166}.border-input{border-color:hsl(var(--input))}.border-primary\/30{border-color:hsl(var(--primary) / .3)}.border-sky-500\/40{border-color:#0ea5e966}.border-transparent{border-color:transparent}.border-violet-500\/40{border-color:#8b5cf666}.bg-accent{background-color:hsl(var(--accent))}.bg-background{background-color:hsl(var(--background))}.bg-black\/50{background-color:#00000080}.bg-border{background-color:hsl(var(--border))}.bg-card{background-color:hsl(var(--card))}.bg-destructive{background-color:hsl(var(--destructive))}.bg-destructive\/5{background-color:hsl(var(--destructive) / .05)}.bg-muted{background-color:hsl(var(--muted))}.bg-muted\/20{background-color:hsl(var(--muted) / .2)}.bg-muted\/30{background-color:hsl(var(--muted) / .3)}.bg-primary{background-color:hsl(var(--primary))}.bg-primary\/5{background-color:hsl(var(--primary) / .05)}.bg-primary\/70{background-color:hsl(var(--primary) / .7)}.bg-secondary{background-color:hsl(var(--secondary))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.p-12{padding:3rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pl-9{padding-left:2.25rem}.pr-8{padding-right:2rem}.pt-0{padding-top:0}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.leading-none{line-height:1}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.text-accent-foreground{color:hsl(var(--accent-foreground))}.text-card-foreground{color:hsl(var(--card-foreground))}.text-destructive{color:hsl(var(--destructive))}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-emerald-500{--tw-text-opacity: 1;color:rgb(16 185 129 / var(--tw-text-opacity, 1))}.text-emerald-600{--tw-text-opacity: 1;color:rgb(5 150 105 / var(--tw-text-opacity, 1))}.text-foreground{color:hsl(var(--foreground))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-sky-600{--tw-text-opacity: 1;color:rgb(2 132 199 / var(--tw-text-opacity, 1))}.text-violet-600{--tw-text-opacity: 1;color:rgb(124 58 237 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline{outline-style:solid}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.file\:border-0::file-selector-button{border-width:0px}.file\:bg-transparent::file-selector-button{background-color:transparent}.file\:text-sm::file-selector-button{font-size:.875rem;line-height:1.25rem}.file\:font-medium::file-selector-button{font-weight:500}.placeholder\:text-muted-foreground::-moz-placeholder{color:hsl(var(--muted-foreground))}.placeholder\:text-muted-foreground::placeholder{color:hsl(var(--muted-foreground))}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive) / .9)}.hover\:bg-muted\/50:hover{background-color:hsl(var(--muted) / .5)}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary) / .9)}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary) / .8)}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:text-foreground:hover{color:hsl(var(--foreground))}.hover\:underline:hover{text-decoration-line:underline}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-ring:focus{--tw-ring-color: hsl(var(--ring))}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-1:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color: hsl(var(--ring))}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:bg-primary{background-color:hsl(var(--primary))}.peer:disabled~.peer-disabled\:cursor-not-allowed{cursor:not-allowed}.peer:disabled~.peer-disabled\:opacity-70{opacity:.7}.data-\[state\=selected\]\:bg-muted[data-state=selected]{background-color:hsl(var(--muted))}.dark\:text-emerald-400:is(.dark *){--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.dark\:text-sky-400:is(.dark *){--tw-text-opacity: 1;color:rgb(56 189 248 / var(--tw-text-opacity, 1))}.dark\:text-violet-400:is(.dark *){--tw-text-opacity: 1;color:rgb(167 139 250 / var(--tw-text-opacity, 1))}@media(min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media(min-width:768px){.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(min-width:1024px){.lg\:col-span-2{grid-column:span 2 / span 2}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-\[260px_1fr\]{grid-template-columns:260px 1fr}}.\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]){padding-right:0}.\[\&\>li\:last-child\>div\:first-child\>span\:last-child\]\:hidden>li:last-child>div:first-child>span:last-child{display:none}.\[\&_tr\:last-child\]\:border-0 tr:last-child{border-width:0px}.\[\&_tr\]\:border-b tr{border-bottom-width:1px}
|