@burdenoff/microfe-timecampus 2026.825.2 → 2026.827.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.
@@ -1,94 +1,94 @@
1
1
  import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
2
- import { StatCard as t } from "../../components/StatCard.js";
3
- import { LoadingSkeleton as n } from "../../components/LoadingSkeleton.js";
4
- import { useAcceptRecommendationMutation as r, useDismissRecommendationMutation as i, useGenerateMyRecommendationsMutation as a, useMyRecommendationsQuery as o, useSnoozeRecommendationMutation as s } from "../../../generated/wspace-operations.js";
5
- import { PageLayout as c } from "../../components/PageLayout.js";
6
- import { StatusBadge as l } from "../../components/StatusBadge.js";
7
- import { EmptyState as u } from "../../components/EmptyState.js";
8
- import { ToastContainer as d, useToast as ee } from "../../components/Toast.js";
2
+ import { cn as t } from "../../utils/helpers.js";
3
+ import { StatCard as n } from "../../components/StatCard.js";
4
+ import { LoadingSkeleton as r } from "../../components/LoadingSkeleton.js";
5
+ import { useAcceptRecommendationMutation as i, useDismissRecommendationMutation as a, useGenerateMyRecommendationsMutation as o, useMyRecommendationPreferencesQuery as s, useMyRecommendationsQuery as c, useSnoozeRecommendationMutation as l, useUpdateMyRecommendationPreferencesMutation as u } from "../../../generated/wspace-operations.js";
6
+ import { PageLayout as d } from "../../components/PageLayout.js";
7
+ import { StatusBadge as f } from "../../components/StatusBadge.js";
8
+ import { EmptyState as p } from "../../components/EmptyState.js";
9
+ import { Modal as m } from "../../components/Modal.js";
10
+ import { ToastContainer as h, useToast as g } from "../../components/Toast.js";
9
11
  import "../../components/index.js";
10
- import { TimeCampusLifeDomain as f } from "../../../generated/wspace-types.js";
11
- import { lifeDomainLabel as p } from "./domains.js";
12
- import { useMemo as m, useState as h } from "react";
13
- import { Briefcase as g, Bus as _, Check as v, CheckCircle2 as y, Circle as b, Clock as x, Coffee as S, GraduationCap as C, Handshake as w, Heart as T, HeartPulse as E, Home as D, Hourglass as O, Lightbulb as k, Moon as A, ShieldCheck as j, Sparkles as M, Users as N, Wallet as P, X as F } from "lucide-react";
14
- import { Fragment as I, jsx as L, jsxs as R } from "react/jsx-runtime";
15
- import { GlassCard as z } from "@burdenoff/fe-libs/ui";
12
+ import { TimeCampusLifeDomain as _ } from "../../../generated/wspace-types.js";
13
+ import { lifeDomainLabel as v } from "./domains.js";
14
+ import { formatEvidenceRef as y, formatHourOfDay as b, humaniseToken as x, parseEvidenceRefs as ee, parseSuggestedAction as S } from "./recommendationEvidence.js";
15
+ import { useMemo as C, useState as w } from "react";
16
+ import { BellOff as T, Briefcase as E, Bus as D, CalendarClock as O, Check as k, CheckCircle2 as te, ChevronDown as ne, Circle as re, Clock as A, Coffee as j, GraduationCap as ie, Handshake as ae, Heart as oe, HeartPulse as se, Home as M, Hourglass as ce, Lightbulb as N, Microscope as le, Moon as ue, ShieldCheck as P, Sparkles as F, Users as I, Wallet as L, X as R } from "lucide-react";
17
+ import { Fragment as de, jsx as z, jsxs as B } from "react/jsx-runtime";
18
+ import { GlassCard as V } from "@burdenoff/fe-libs/ui";
16
19
  //#region src/timecampus/pages/lifetime/RecommendationsPage.tsx
17
- var B = {
20
+ var H = {
18
21
  New: "NEW",
19
22
  Snoozed: "SNOOZED",
20
23
  Accepted: "ACCEPTED",
21
24
  Dismissed: "DISMISSED",
22
25
  Expired: "EXPIRED"
23
- }, te = [
26
+ }, fe = [
24
27
  {
25
- key: B.New,
28
+ key: H.New,
26
29
  label: "New"
27
30
  },
28
31
  {
29
- key: B.Snoozed,
32
+ key: H.Snoozed,
30
33
  label: "Snoozed"
31
34
  },
32
35
  {
33
- key: B.Accepted,
36
+ key: H.Accepted,
34
37
  label: "Accepted"
35
38
  },
36
39
  {
37
- key: B.Dismissed,
40
+ key: H.Dismissed,
38
41
  label: "Dismissed"
39
42
  }
40
43
  ];
41
- function V(e) {
42
- return e === B.New || e === B.Snoozed;
44
+ function U(e) {
45
+ return e === H.New || e === H.Snoozed;
43
46
  }
44
- function ne() {
47
+ function pe() {
45
48
  return new Date(Date.now() + 6048e5).toISOString();
46
49
  }
47
- var H = (e) => Math.min(1, Math.max(0, e)), U = (e) => `${Math.round(H(e) * 100)}%`;
48
- function W(e) {
50
+ var W = (e) => Math.min(1, Math.max(0, e)), G = (e) => `${Math.round(W(e) * 100)}%`;
51
+ function K(e) {
49
52
  return new Date(e).toLocaleDateString(void 0, {
50
53
  month: "short",
51
54
  day: "numeric",
52
55
  year: "numeric"
53
56
  });
54
57
  }
55
- function G(e) {
56
- return e.toLowerCase().split(/[_\s]+/).filter(Boolean).map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join(" ");
57
- }
58
- var K = {
59
- [f.Work]: g,
60
- [f.Sleep]: A,
61
- [f.Health]: E,
62
- [f.Family]: N,
63
- [f.Learning]: C,
64
- [f.Leisure]: S,
65
- [f.Commute]: _,
66
- [f.Waiting]: O,
67
- [f.Chores]: D,
68
- [f.SelfCare]: T,
69
- [f.Social]: w,
70
- [f.FinanceAdmin]: P,
71
- [f.Reflection]: M,
72
- [f.Other]: b
58
+ var q = Array.from({ length: 24 }, (e, t) => t), me = {
59
+ [_.Work]: E,
60
+ [_.Sleep]: ue,
61
+ [_.Health]: se,
62
+ [_.Family]: I,
63
+ [_.Learning]: ie,
64
+ [_.Leisure]: j,
65
+ [_.Commute]: D,
66
+ [_.Waiting]: ce,
67
+ [_.Chores]: M,
68
+ [_.SelfCare]: oe,
69
+ [_.Social]: ae,
70
+ [_.FinanceAdmin]: L,
71
+ [_.Reflection]: F,
72
+ [_.Other]: re
73
73
  };
74
- function q(e) {
75
- return e ? K[e] ?? k : k;
74
+ function he(e) {
75
+ return e ? me[e] ?? N : N;
76
76
  }
77
- function J(e) {
77
+ function ge(e) {
78
78
  switch (e) {
79
- case B.Accepted: return {
79
+ case H.Accepted: return {
80
80
  variant: "success",
81
81
  label: "Accepted"
82
82
  };
83
- case B.Snoozed: return {
83
+ case H.Snoozed: return {
84
84
  variant: "warning",
85
85
  label: "Snoozed"
86
86
  };
87
- case B.Dismissed: return {
87
+ case H.Dismissed: return {
88
88
  variant: "muted",
89
89
  label: "Dismissed"
90
90
  };
91
- case B.Expired: return {
91
+ case H.Expired: return {
92
92
  variant: "muted",
93
93
  label: "Expired"
94
94
  };
@@ -98,114 +98,370 @@ function J(e) {
98
98
  };
99
99
  }
100
100
  }
101
- var Y = "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50", X = "inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50", Z = "inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary";
102
- function re({ rec: e, busy: t, onAccept: n, onSnooze: r, onDismiss: i }) {
103
- let a = q(e.domain), o = V(e.state), s = J(e.state);
104
- return /* @__PURE__ */ R(z, {
101
+ var J = "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50", Y = "inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50", X = "inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary";
102
+ function _e({ checked: e, onChange: n, label: r, disabled: i }) {
103
+ return /* @__PURE__ */ z("button", {
104
+ type: "button",
105
+ role: "switch",
106
+ "aria-checked": e,
107
+ "aria-label": r,
108
+ disabled: i,
109
+ onClick: () => n(!e),
110
+ className: t("relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border transition-colors disabled:cursor-not-allowed disabled:opacity-50", e ? "border-transparent bg-action-primary-bg" : "border-border-subtle bg-bg-sunken"),
111
+ children: /* @__PURE__ */ z("span", { className: t("inline-block h-3.5 w-3.5 transform rounded-full shadow-sm transition-transform", e ? "translate-x-4 bg-action-primary-text" : "translate-x-0.5 bg-text-secondary") })
112
+ });
113
+ }
114
+ function ve({ rec: e, busy: n, muted: r, onAccept: i, onSnooze: a, onDismiss: o, onMuteKind: s }) {
115
+ let c = he(e.domain), l = U(e.state), u = ge(e.state), [d, p] = w(!1), m = ee(e.evidenceRefs), h = S(e.suggestedAction), g = `rec-evidence-${e.id}`;
116
+ return /* @__PURE__ */ B(V, {
105
117
  treatment: "glass",
106
118
  className: "flex flex-col gap-3 p-4 sm:p-5",
107
119
  children: [
108
- /* @__PURE__ */ R("div", {
120
+ /* @__PURE__ */ B("div", {
109
121
  className: "flex items-start justify-between gap-3",
110
- children: [/* @__PURE__ */ R("div", {
122
+ children: [/* @__PURE__ */ B("div", {
111
123
  className: "flex min-w-0 items-center gap-3",
112
- children: [/* @__PURE__ */ L("span", {
124
+ children: [/* @__PURE__ */ z("span", {
113
125
  className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary",
114
- children: /* @__PURE__ */ L(a, { className: "h-5 w-5" })
115
- }), /* @__PURE__ */ R("div", {
126
+ children: /* @__PURE__ */ z(c, { className: "h-5 w-5" })
127
+ }), /* @__PURE__ */ B("div", {
116
128
  className: "min-w-0",
117
- children: [/* @__PURE__ */ L("p", {
129
+ children: [/* @__PURE__ */ z("p", {
118
130
  className: "truncate text-xs font-medium uppercase tracking-wide text-text-secondary",
119
- children: G(e.kind)
120
- }), /* @__PURE__ */ L("h3", {
131
+ children: x(e.kind)
132
+ }), /* @__PURE__ */ z("h3", {
121
133
  className: "mt-0.5 text-base font-semibold text-text-primary",
122
134
  children: e.title
123
135
  })]
124
136
  })]
125
- }), e.confidence != null && /* @__PURE__ */ L(l, {
137
+ }), e.confidence != null && /* @__PURE__ */ z(f, {
126
138
  variant: "info",
127
- label: `${U(e.confidence)} confident`,
139
+ label: `${G(e.confidence)} confident`,
128
140
  className: "shrink-0"
129
141
  })]
130
142
  }),
131
- /* @__PURE__ */ L("p", {
143
+ /* @__PURE__ */ z("p", {
132
144
  className: "text-sm text-text-secondary",
133
145
  children: e.rationale
134
146
  }),
135
- /* @__PURE__ */ R("div", {
147
+ /* @__PURE__ */ B("div", {
136
148
  className: "flex flex-wrap items-center gap-1.5",
137
149
  children: [
138
- e.domain && /* @__PURE__ */ L("span", {
139
- className: Z,
140
- children: p(e.domain)
150
+ e.domain && /* @__PURE__ */ z("span", {
151
+ className: X,
152
+ children: v(e.domain)
141
153
  }),
142
- e.valueAlignment != null && /* @__PURE__ */ R("span", {
143
- className: Z,
154
+ e.valueAlignment != null && /* @__PURE__ */ B("span", {
155
+ className: X,
144
156
  children: [
145
- /* @__PURE__ */ L(j, { className: "h-3 w-3" }),
157
+ /* @__PURE__ */ z(P, { className: "h-3 w-3" }),
146
158
  "Aligns with your values · ",
147
- U(e.valueAlignment)
159
+ G(e.valueAlignment)
148
160
  ]
149
161
  }),
150
- e.state === B.Snoozed && e.snoozeUntil && /* @__PURE__ */ R("span", {
151
- className: Z,
162
+ e.state === H.Snoozed && e.snoozeUntil && /* @__PURE__ */ B("span", {
163
+ className: X,
152
164
  children: [
153
- /* @__PURE__ */ L(x, { className: "h-3 w-3" }),
165
+ /* @__PURE__ */ z(A, { className: "h-3 w-3" }),
154
166
  "Snoozed until ",
155
- W(e.snoozeUntil)
167
+ K(e.snoozeUntil)
168
+ ]
169
+ }),
170
+ l && e.expiresAt && /* @__PURE__ */ B("span", {
171
+ className: X,
172
+ children: [
173
+ /* @__PURE__ */ z(O, { className: "h-3 w-3" }),
174
+ "Expires ",
175
+ K(e.expiresAt)
156
176
  ]
157
177
  })
158
178
  ]
159
179
  }),
160
- o ? /* @__PURE__ */ R("div", {
180
+ h && /* @__PURE__ */ B("div", {
181
+ className: "rounded-lg border border-border-seam bg-bg-sunken/40 p-3",
182
+ children: [
183
+ /* @__PURE__ */ z("p", {
184
+ className: "text-xs font-medium uppercase tracking-wide text-text-secondary",
185
+ children: "Suggested action"
186
+ }),
187
+ /* @__PURE__ */ z("p", {
188
+ className: "mt-1 text-sm font-medium text-text-primary",
189
+ children: x(h.type)
190
+ }),
191
+ h.details.length > 0 && /* @__PURE__ */ z("dl", {
192
+ className: "mt-1.5 flex flex-wrap gap-x-4 gap-y-1",
193
+ children: h.details.map((e) => /* @__PURE__ */ B("div", {
194
+ className: "flex items-center gap-1.5",
195
+ children: [/* @__PURE__ */ z("dt", {
196
+ className: "text-xs text-text-secondary",
197
+ children: e.key
198
+ }), /* @__PURE__ */ z("dd", {
199
+ className: "text-xs font-medium tabular-nums text-text-primary",
200
+ children: e.value
201
+ })]
202
+ }, e.key))
203
+ })
204
+ ]
205
+ }),
206
+ m.length > 0 && /* @__PURE__ */ B("div", {
207
+ className: "rounded-lg border border-border-seam bg-bg-sunken/40",
208
+ children: [/* @__PURE__ */ B("button", {
209
+ type: "button",
210
+ onClick: () => p((e) => !e),
211
+ "aria-expanded": d,
212
+ "aria-controls": g,
213
+ className: "flex w-full items-center justify-between gap-2 px-3 py-2 text-left",
214
+ children: [/* @__PURE__ */ B("span", {
215
+ className: "flex min-w-0 items-center gap-2",
216
+ children: [/* @__PURE__ */ z(le, { className: "h-3.5 w-3.5 shrink-0 text-text-secondary" }), /* @__PURE__ */ B("span", {
217
+ className: "truncate text-xs text-text-secondary",
218
+ children: [
219
+ "Evidence: ",
220
+ y(m[0]),
221
+ m.length > 1 ? ` · +${m.length - 1} more` : ""
222
+ ]
223
+ })]
224
+ }), /* @__PURE__ */ z(ne, { className: t("h-4 w-4 shrink-0 text-text-secondary transition-transform", d && "rotate-180") })]
225
+ }), d && /* @__PURE__ */ B("dl", {
226
+ id: g,
227
+ className: "space-y-1.5 border-t border-border-seam px-3 py-2.5",
228
+ children: [m.map((e, t) => /* @__PURE__ */ B("div", {
229
+ className: "flex items-baseline justify-between gap-3",
230
+ children: [/* @__PURE__ */ z("dt", {
231
+ className: "text-xs text-text-secondary",
232
+ children: y(e).split(": ")[0]
233
+ }), /* @__PURE__ */ z("dd", {
234
+ className: "text-xs font-medium tabular-nums text-text-primary",
235
+ children: y(e).split(": ").slice(1).join(": ")
236
+ })]
237
+ }, `${e.metric}-${e.period ?? e.domain ?? t}`)), /* @__PURE__ */ z("p", {
238
+ className: "pt-1 text-xs text-text-tertiary",
239
+ children: "Measured from your own tracked week. Nothing is applied until you accept."
240
+ })]
241
+ })]
242
+ }),
243
+ l ? /* @__PURE__ */ B("div", {
161
244
  className: "flex flex-wrap items-center gap-2 pt-1",
162
245
  children: [
163
- /* @__PURE__ */ R("button", {
246
+ /* @__PURE__ */ B("button", {
247
+ type: "button",
248
+ onClick: () => i(e.id),
249
+ disabled: n,
250
+ className: J,
251
+ children: [/* @__PURE__ */ z(k, { className: "h-4 w-4" }), "Accept"]
252
+ }),
253
+ /* @__PURE__ */ B("button", {
164
254
  type: "button",
165
- onClick: () => n(e.id),
166
- disabled: t,
255
+ onClick: () => a(e.id),
256
+ disabled: n,
167
257
  className: Y,
168
- children: [/* @__PURE__ */ L(v, { className: "h-4 w-4" }), "Accept"]
258
+ children: [/* @__PURE__ */ z(A, { className: "h-4 w-4" }), "Snooze"]
169
259
  }),
170
- /* @__PURE__ */ R("button", {
260
+ /* @__PURE__ */ B("button", {
171
261
  type: "button",
172
- onClick: () => r(e.id),
173
- disabled: t,
174
- className: X,
175
- children: [/* @__PURE__ */ L(x, { className: "h-4 w-4" }), "Snooze"]
262
+ onClick: () => o(e),
263
+ disabled: n,
264
+ className: Y,
265
+ children: [/* @__PURE__ */ z(R, { className: "h-4 w-4" }), "Dismiss"]
176
266
  }),
177
- /* @__PURE__ */ R("button", {
267
+ /* @__PURE__ */ B("button", {
178
268
  type: "button",
179
- onClick: () => i(e.id),
180
- disabled: t,
181
- className: X,
182
- children: [/* @__PURE__ */ L(F, { className: "h-4 w-4" }), "Dismiss"]
269
+ onClick: () => s(e.kind),
270
+ disabled: n || r,
271
+ className: Y,
272
+ title: r ? `${x(e.kind)} suggestions are already muted` : `Stop generating ${x(e.kind)} suggestions`,
273
+ children: [/* @__PURE__ */ z(T, { className: "h-4 w-4" }), r ? "Muted" : "Mute this kind"]
183
274
  })
184
275
  ]
185
- }) : /* @__PURE__ */ R("div", {
186
- className: "flex items-center justify-between gap-2 pt-1",
187
- children: [/* @__PURE__ */ L(l, {
188
- variant: s.variant,
189
- label: s.label
190
- }), /* @__PURE__ */ L("span", {
276
+ }) : /* @__PURE__ */ B("div", {
277
+ className: "flex flex-col gap-2 pt-1",
278
+ children: [/* @__PURE__ */ B("div", {
279
+ className: "flex items-center justify-between gap-2",
280
+ children: [/* @__PURE__ */ z(f, {
281
+ variant: u.variant,
282
+ label: u.label
283
+ }), /* @__PURE__ */ z("span", {
284
+ className: "text-xs text-text-secondary",
285
+ children: K(e.createdAt)
286
+ })]
287
+ }), e.state === H.Dismissed && e.dismissReason && /* @__PURE__ */ B("p", {
191
288
  className: "text-xs text-text-secondary",
192
- children: W(e.createdAt)
289
+ children: ["Reason given: ", e.dismissReason]
290
+ })]
291
+ })
292
+ ]
293
+ });
294
+ }
295
+ function ye({ prefs: e, loading: n, saving: i, onToggleOutputGuard: a, onQuietHoursChange: o, onUnmuteKind: s }) {
296
+ if (n) return /* @__PURE__ */ z(r, {
297
+ variant: "card",
298
+ count: 1
299
+ });
300
+ if (!e) return null;
301
+ let c = b(e.quietHoursStart), l = b(e.quietHoursEnd), u = c !== null && l !== null, d = "rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg/40 disabled:opacity-50";
302
+ return /* @__PURE__ */ B(V, {
303
+ treatment: "glass",
304
+ className: "p-4 sm:p-5",
305
+ children: [
306
+ /* @__PURE__ */ B("div", {
307
+ className: "flex items-center gap-2",
308
+ children: [/* @__PURE__ */ z(P, { className: "h-4 w-4 text-text-secondary" }), /* @__PURE__ */ z("h3", {
309
+ className: "text-sm font-semibold text-text-primary",
310
+ children: "Guardrails"
193
311
  })]
312
+ }),
313
+ /* @__PURE__ */ z("p", {
314
+ className: "mt-1 text-xs text-text-secondary",
315
+ children: "What TimeCampus is allowed to suggest, and when."
316
+ }),
317
+ /* @__PURE__ */ B("div", {
318
+ className: "mt-4 space-y-4",
319
+ children: [
320
+ /* @__PURE__ */ B("div", {
321
+ className: "flex items-start justify-between gap-3",
322
+ children: [/* @__PURE__ */ B("div", {
323
+ className: "min-w-0",
324
+ children: [/* @__PURE__ */ z("p", {
325
+ className: "text-sm font-medium text-text-primary",
326
+ children: "Never suggest more work"
327
+ }), /* @__PURE__ */ z("p", {
328
+ className: "mt-0.5 text-xs text-text-secondary",
329
+ children: "Output-increasing suggestions are filtered out before they reach you."
330
+ })]
331
+ }), /* @__PURE__ */ z(_e, {
332
+ checked: e.neverOptimiseForOutput,
333
+ disabled: i,
334
+ label: "Never suggest more work",
335
+ onChange: a
336
+ })]
337
+ }),
338
+ /* @__PURE__ */ B("div", {
339
+ className: "border-t border-border-seam pt-4",
340
+ children: [
341
+ /* @__PURE__ */ B("div", {
342
+ className: "flex items-center justify-between gap-3",
343
+ children: [/* @__PURE__ */ z("p", {
344
+ className: "text-sm font-medium text-text-primary",
345
+ children: "Quiet hours"
346
+ }), /* @__PURE__ */ z("span", {
347
+ className: "text-xs text-text-secondary",
348
+ children: u ? `${c}–${l}` : "Not set"
349
+ })]
350
+ }),
351
+ /* @__PURE__ */ z("p", {
352
+ className: "mt-0.5 text-xs text-text-secondary",
353
+ children: "No nudges are sent inside this window."
354
+ }),
355
+ /* @__PURE__ */ B("div", {
356
+ className: "mt-2 flex flex-wrap items-center gap-2",
357
+ children: [
358
+ /* @__PURE__ */ B("label", {
359
+ className: "flex items-center gap-1.5 text-xs text-text-secondary",
360
+ children: ["From", /* @__PURE__ */ B("select", {
361
+ className: d,
362
+ disabled: i,
363
+ value: e.quietHoursStart ?? "",
364
+ onChange: (t) => o(t.target.value === "" ? null : Number(t.target.value), e.quietHoursEnd ?? null),
365
+ children: [/* @__PURE__ */ z("option", {
366
+ value: "",
367
+ children: "—"
368
+ }), q.map((e) => /* @__PURE__ */ z("option", {
369
+ value: e,
370
+ children: b(e)
371
+ }, e))]
372
+ })]
373
+ }),
374
+ /* @__PURE__ */ B("label", {
375
+ className: "flex items-center gap-1.5 text-xs text-text-secondary",
376
+ children: ["To", /* @__PURE__ */ B("select", {
377
+ className: d,
378
+ disabled: i,
379
+ value: e.quietHoursEnd ?? "",
380
+ onChange: (t) => o(e.quietHoursStart ?? null, t.target.value === "" ? null : Number(t.target.value)),
381
+ children: [/* @__PURE__ */ z("option", {
382
+ value: "",
383
+ children: "—"
384
+ }), q.map((e) => /* @__PURE__ */ z("option", {
385
+ value: e,
386
+ children: b(e)
387
+ }, e))]
388
+ })]
389
+ }),
390
+ u && /* @__PURE__ */ z("button", {
391
+ type: "button",
392
+ disabled: i,
393
+ onClick: () => o(null, null),
394
+ className: "text-xs font-medium text-text-secondary underline-offset-2 hover:text-text-primary hover:underline disabled:opacity-50",
395
+ children: "Clear"
396
+ })
397
+ ]
398
+ })
399
+ ]
400
+ }),
401
+ /* @__PURE__ */ B("div", {
402
+ className: "border-t border-border-seam pt-4",
403
+ children: [/* @__PURE__ */ B("div", {
404
+ className: "flex items-center justify-between gap-3",
405
+ children: [/* @__PURE__ */ z("p", {
406
+ className: "text-sm font-medium text-text-primary",
407
+ children: "Muted kinds"
408
+ }), /* @__PURE__ */ z("span", {
409
+ className: X,
410
+ children: e.mutedKinds.length
411
+ })]
412
+ }), e.mutedKinds.length === 0 ? /* @__PURE__ */ z("p", {
413
+ className: "mt-0.5 text-xs text-text-secondary",
414
+ children: "Nothing muted — mute a kind from any suggestion to stop it coming back."
415
+ }) : /* @__PURE__ */ z("div", {
416
+ className: "mt-2 flex flex-wrap gap-1.5",
417
+ children: e.mutedKinds.map((e) => /* @__PURE__ */ B("button", {
418
+ type: "button",
419
+ disabled: i,
420
+ onClick: () => s(e),
421
+ className: t(X, "hover:text-text-primary disabled:opacity-50"),
422
+ title: `Unmute ${x(e)} suggestions`,
423
+ children: [x(e), /* @__PURE__ */ z(R, { className: "h-3 w-3" })]
424
+ }, e))
425
+ })]
426
+ }),
427
+ /* @__PURE__ */ B("div", {
428
+ className: "border-t border-border-seam pt-4",
429
+ children: [/* @__PURE__ */ B("div", {
430
+ className: "flex items-center justify-between gap-3",
431
+ children: [/* @__PURE__ */ z("p", {
432
+ className: "text-sm font-medium text-text-primary",
433
+ children: "Cadence"
434
+ }), /* @__PURE__ */ z("span", {
435
+ className: "text-xs text-text-secondary",
436
+ children: x(e.cadence)
437
+ })]
438
+ }), e.optedInDomains.length > 0 && /* @__PURE__ */ B(de, { children: [/* @__PURE__ */ z("p", {
439
+ className: "mt-3 text-sm font-medium text-text-primary",
440
+ children: "Opted-in domains"
441
+ }), /* @__PURE__ */ z("div", {
442
+ className: "mt-1.5 flex flex-wrap gap-1.5",
443
+ children: e.optedInDomains.map((e) => /* @__PURE__ */ z("span", {
444
+ className: X,
445
+ children: v(e)
446
+ }, e))
447
+ })] })]
448
+ })
449
+ ]
194
450
  })
195
451
  ]
196
452
  });
197
453
  }
198
- function ie(e) {
454
+ function be(e) {
199
455
  switch (e) {
200
- case B.Snoozed: return {
456
+ case H.Snoozed: return {
201
457
  title: "Nothing snoozed",
202
458
  description: "Suggestions you snooze for later will wait here quietly until they're worth another look."
203
459
  };
204
- case B.Accepted: return {
460
+ case H.Accepted: return {
205
461
  title: "Nothing accepted yet",
206
462
  description: "When you accept a suggestion it moves here, so you can see the changes you chose to make."
207
463
  };
208
- case B.Dismissed: return {
464
+ case H.Dismissed: return {
209
465
  title: "Nothing dismissed",
210
466
  description: "Suggestions you dismiss are kept here — TimeCampus learns from them and won't keep nagging."
211
467
  };
@@ -215,208 +471,297 @@ function ie(e) {
215
471
  };
216
472
  }
217
473
  }
218
- function Q() {
219
- let { workspaceId: l } = e(), { toasts: f, addToast: p, dismissToast: g } = ee(), [_, v] = h(B.New), [b, S] = h(null), { data: C, loading: w, error: T, refetch: E } = o({
474
+ function xe() {
475
+ let { workspaceId: t } = e(), { toasts: f, addToast: _, dismissToast: v } = g(), [y, b] = w(H.New), [ee, S] = w(null), [T, E] = w(null), [D, O] = w(""), { data: k, loading: ne, error: re, refetch: j } = c({
220
476
  variables: { state: null },
221
- skip: !l
222
- }), [D, { loading: O }] = a(), [k] = r(), [A] = i(), [N] = s(), P = m(() => C?.myRecommendations ?? [], [C?.myRecommendations]), F = m(() => {
477
+ skip: !t
478
+ }), { data: ie, loading: ae, refetch: oe } = s({ skip: !t }), [se, { loading: M }] = o(), [ce] = i(), [N] = a(), [le] = l(), [ue, { loading: I }] = u(), L = ie?.myRecommendationPreferences, U = C(() => new Set(L?.mutedKinds ?? []), [L?.mutedKinds]), W = C(() => k?.myRecommendations ?? [], [k?.myRecommendations]), G = C(() => {
223
479
  let e = {};
224
- for (let t of P) e[t.state] = (e[t.state] ?? 0) + 1;
480
+ for (let t of W) e[t.state] = (e[t.state] ?? 0) + 1;
225
481
  return e;
226
- }, [P]), V = m(() => P.filter((e) => e.state === _), [P, _]), H = F[B.New] ?? 0, U = F[B.Accepted] ?? 0, W = F[B.Snoozed] ?? 0, G = async () => {
482
+ }, [W]), K = C(() => W.filter((e) => e.state === y), [W, y]), q = G[H.New] ?? 0, me = G[H.Accepted] ?? 0, he = G[H.Snoozed] ?? 0, ge = async () => {
227
483
  try {
228
- let e = (await D()).data?.generateMyRecommendations ?? [];
229
- p({
484
+ let e = (await se()).data?.generateMyRecommendations ?? [];
485
+ _({
230
486
  title: e.length > 0 ? `${e.length} suggestion${e.length === 1 ? "" : "s"} ready` : "No new suggestions",
231
487
  description: e.length > 0 ? "Fresh, values-aware ideas are in your New feed." : "You're all caught up — nothing new to suggest right now.",
232
488
  variant: e.length > 0 ? "success" : "info"
233
- }), v(B.New), await E();
489
+ }), b(H.New), await j();
234
490
  } catch {
235
- p({
491
+ _({
236
492
  title: "Couldn't generate recommendations",
237
493
  description: "Please try again in a moment.",
238
494
  variant: "error"
239
495
  });
240
496
  }
241
- }, K = async (e) => {
497
+ }, X = async (e) => {
242
498
  S(e);
243
499
  try {
244
- await k({ variables: { id: e } }), p({
500
+ await ce({ variables: { id: e } }), _({
245
501
  title: "Accepted — nice",
246
502
  variant: "success"
247
- }), await E();
503
+ }), await j();
248
504
  } catch {
249
- p({
505
+ _({
250
506
  title: "Couldn't accept that one",
251
507
  variant: "error"
252
508
  });
253
509
  } finally {
254
510
  S(null);
255
511
  }
256
- }, q = async (e) => {
512
+ }, _e = async (e) => {
257
513
  S(e);
258
514
  try {
259
- await N({ variables: {
515
+ await le({ variables: {
260
516
  id: e,
261
- until: ne()
262
- } }), p({
517
+ until: pe()
518
+ } }), _({
263
519
  title: "Snoozed for a week",
264
520
  description: "It'll resurface later — no nagging in between.",
265
521
  variant: "info"
266
- }), await E();
522
+ }), await j();
267
523
  } catch {
268
- p({
524
+ _({
269
525
  title: "Couldn't snooze that one",
270
526
  variant: "error"
271
527
  });
272
528
  } finally {
273
529
  S(null);
274
530
  }
275
- }, J = async (e) => {
276
- S(e);
531
+ }, xe = (e) => {
532
+ O(""), E(e);
533
+ }, Se = async () => {
534
+ let e = T;
535
+ if (!e) return;
536
+ let t = D.trim();
537
+ S(e.id);
277
538
  try {
278
- await A({ variables: { id: e } }), p({
539
+ await N({ variables: {
540
+ id: e.id,
541
+ reason: t.length > 0 ? t : null
542
+ } }), _({
279
543
  title: "Dismissed",
280
544
  variant: "success"
281
- }), await E();
545
+ }), E(null), O(""), await j();
282
546
  } catch {
283
- p({
547
+ _({
284
548
  title: "Couldn't dismiss that one",
285
549
  variant: "error"
286
550
  });
287
551
  } finally {
288
552
  S(null);
289
553
  }
290
- }, X = /* @__PURE__ */ R("button", {
554
+ }, Z = async (e, t) => {
555
+ try {
556
+ await ue({ variables: { input: e } }), _({
557
+ title: t,
558
+ variant: "success"
559
+ }), await oe();
560
+ } catch {
561
+ _({
562
+ title: "Couldn't save that guardrail",
563
+ description: "Please try again in a moment.",
564
+ variant: "error"
565
+ });
566
+ }
567
+ }, Ce = (e) => void Z({ neverOptimiseForOutput: e }, e ? "Output-increasing suggestions are off" : "Output-increasing suggestions allowed"), we = (e, t) => void Z({
568
+ quietHoursStart: e,
569
+ quietHoursEnd: t
570
+ }, e === null && t === null ? "Quiet hours cleared" : "Quiet hours saved"), Te = (e) => {
571
+ U.has(e) || Z({ mutedKinds: [...U, e] }, `${x(e)} suggestions muted`);
572
+ }, Ee = /* @__PURE__ */ z(ye, {
573
+ prefs: L,
574
+ loading: ae,
575
+ saving: I,
576
+ onToggleOutputGuard: Ce,
577
+ onQuietHoursChange: we,
578
+ onUnmuteKind: (e) => void Z({ mutedKinds: [...U].filter((t) => t !== e) }, `${x(e)} suggestions unmuted`)
579
+ }), Q = /* @__PURE__ */ B("button", {
291
580
  type: "button",
292
- onClick: G,
293
- disabled: O || !l,
294
- className: Y,
295
- children: [/* @__PURE__ */ L(M, { className: "h-4 w-4" }), O ? "Generating…" : "Generate recommendations"]
296
- }), Z = "Gentle, values-aware nudges built from your real timeline — protect a focus block, reclaim a commute, guard family time. Accept the ones that fit, snooze the rest, and dismiss what doesn't. TimeCampus never optimises for more output — only for time that matches what you value.", Q = /* @__PURE__ */ L(z, {
581
+ onClick: ge,
582
+ disabled: M || !t,
583
+ className: J,
584
+ children: [/* @__PURE__ */ z(F, { className: "h-4 w-4" }), M ? "Generating…" : "Generate recommendations"]
585
+ }), $ = "Gentle, values-aware nudges built from your real timeline — protect a focus block, reclaim a commute, guard family time. Accept the ones that fit, snooze the rest, and dismiss what doesn't. TimeCampus never optimises for more output — only for time that matches what you value.", De = /* @__PURE__ */ z(V, {
297
586
  treatment: "insight",
298
587
  className: "mt-6 p-4 sm:p-5",
299
- children: /* @__PURE__ */ R("div", {
588
+ children: /* @__PURE__ */ B("div", {
300
589
  className: "flex items-start gap-3",
301
- children: [/* @__PURE__ */ L("span", {
590
+ children: [/* @__PURE__ */ z("span", {
302
591
  className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-status-info-bg text-status-info-text",
303
- children: /* @__PURE__ */ L(j, { className: "h-5 w-5" })
304
- }), /* @__PURE__ */ R("div", {
592
+ children: /* @__PURE__ */ z(P, { className: "h-5 w-5" })
593
+ }), /* @__PURE__ */ B("div", {
305
594
  className: "min-w-0",
306
- children: [/* @__PURE__ */ L("h3", {
595
+ children: [/* @__PURE__ */ z("h3", {
307
596
  className: "text-sm font-semibold text-text-primary",
308
597
  children: "Never optimises for more output."
309
- }), /* @__PURE__ */ L("p", {
598
+ }), /* @__PURE__ */ z("p", {
310
599
  className: "mt-1 text-sm text-text-secondary",
311
600
  children: "Every suggestion protects something you value — rest, focus, relationships — not your throughput. Snooze or dismiss freely; TimeCampus takes the hint and won't keep nagging."
312
601
  })]
313
602
  })]
314
603
  })
315
604
  });
316
- if (!l || w) return /* @__PURE__ */ L(c, {
605
+ if (!t || ne) return /* @__PURE__ */ z(d, {
317
606
  title: "Recommendations",
318
607
  description: "Values-aware suggestions from your real timeline",
319
- purpose: Z,
320
- actions: X,
321
- children: /* @__PURE__ */ L(n, {
608
+ purpose: $,
609
+ actions: Q,
610
+ children: /* @__PURE__ */ z(r, {
322
611
  variant: "list",
323
612
  count: 4
324
613
  })
325
614
  });
326
- if (T) return /* @__PURE__ */ L(c, {
615
+ if (re) return /* @__PURE__ */ z(d, {
327
616
  title: "Recommendations",
328
617
  description: "Values-aware suggestions from your real timeline",
329
- purpose: Z,
330
- actions: X,
331
- children: /* @__PURE__ */ L("div", {
618
+ purpose: $,
619
+ actions: Q,
620
+ children: /* @__PURE__ */ z("div", {
332
621
  className: "rounded-xl border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
333
622
  children: "Couldn't load your recommendations right now. Please try again in a moment."
334
623
  })
335
624
  });
336
- let ae = P.length > 0, $ = ie(_);
337
- return /* @__PURE__ */ R(c, {
625
+ let Oe = W.length > 0, ke = be(y);
626
+ return /* @__PURE__ */ B(d, {
338
627
  title: "Recommendations",
339
628
  description: "Values-aware suggestions from your real timeline",
340
- purpose: Z,
341
- actions: X,
342
- children: [ae ? /* @__PURE__ */ R("div", {
343
- className: "space-y-6",
344
- children: [
345
- /* @__PURE__ */ R("div", {
346
- className: "grid grid-cols-1 gap-4 sm:grid-cols-3",
629
+ purpose: $,
630
+ actions: Q,
631
+ children: [
632
+ Oe ? /* @__PURE__ */ B("div", {
633
+ className: "grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]",
634
+ children: [/* @__PURE__ */ B("div", {
635
+ className: "min-w-0 space-y-6",
347
636
  children: [
348
- /* @__PURE__ */ L(t, {
349
- featured: !0,
350
- title: "New for you",
351
- value: String(H),
352
- icon: /* @__PURE__ */ L(M, { className: "h-5 w-5" }),
353
- description: "Awaiting your call"
637
+ /* @__PURE__ */ B("div", {
638
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-3",
639
+ children: [
640
+ /* @__PURE__ */ z(n, {
641
+ featured: !0,
642
+ title: "New for you",
643
+ value: String(q),
644
+ icon: /* @__PURE__ */ z(F, { className: "h-5 w-5" }),
645
+ description: "Awaiting your call"
646
+ }),
647
+ /* @__PURE__ */ z(n, {
648
+ title: "Accepted",
649
+ value: String(me),
650
+ icon: /* @__PURE__ */ z(te, { className: "h-5 w-5" }),
651
+ description: "Changes you chose to make"
652
+ }),
653
+ /* @__PURE__ */ z(n, {
654
+ title: "Snoozed",
655
+ value: String(he),
656
+ icon: /* @__PURE__ */ z(A, { className: "h-5 w-5" }),
657
+ description: "Waiting for a better moment"
658
+ })
659
+ ]
354
660
  }),
355
- /* @__PURE__ */ L(t, {
356
- title: "Accepted",
357
- value: String(U),
358
- icon: /* @__PURE__ */ L(y, { className: "h-5 w-5" }),
359
- description: "Changes you chose to make"
661
+ /* @__PURE__ */ z("div", {
662
+ className: "overflow-x-auto",
663
+ children: /* @__PURE__ */ z("div", {
664
+ className: "inline-flex items-center rounded-lg border border-border-subtle bg-bg-sunken/50 p-0.5",
665
+ role: "tablist",
666
+ "aria-label": "Filter recommendations by state",
667
+ children: fe.map((e) => {
668
+ let t = e.key === y, n = G[e.key] ?? 0;
669
+ return /* @__PURE__ */ B("button", {
670
+ type: "button",
671
+ role: "tab",
672
+ "aria-selected": t,
673
+ onClick: () => b(e.key),
674
+ className: t ? "flex items-center gap-1.5 rounded-md bg-bg-surface px-3 py-1.5 text-sm font-medium text-text-primary shadow-sm" : "flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary",
675
+ children: [e.label, /* @__PURE__ */ z("span", {
676
+ className: t ? "rounded-full bg-action-primary-bg/15 px-1.5 text-xs font-semibold text-primary" : "rounded-full bg-bg-sunken px-1.5 text-xs font-semibold text-text-secondary",
677
+ children: n
678
+ })]
679
+ }, e.key);
680
+ })
681
+ })
360
682
  }),
361
- /* @__PURE__ */ L(t, {
362
- title: "Snoozed",
363
- value: String(W),
364
- icon: /* @__PURE__ */ L(x, { className: "h-5 w-5" }),
365
- description: "Waiting for a better moment"
366
- })
683
+ K.length === 0 ? /* @__PURE__ */ z(p, {
684
+ illustration: "empty-data",
685
+ title: ke.title,
686
+ description: ke.description,
687
+ action: y === H.New ? Q : void 0
688
+ }) : /* @__PURE__ */ z("div", {
689
+ className: "space-y-4",
690
+ children: K.map((e) => /* @__PURE__ */ z(ve, {
691
+ rec: e,
692
+ busy: ee === e.id || I,
693
+ muted: U.has(e.kind),
694
+ onAccept: X,
695
+ onSnooze: _e,
696
+ onDismiss: xe,
697
+ onMuteKind: Te
698
+ }, e.id))
699
+ }),
700
+ De
367
701
  ]
368
- }),
369
- /* @__PURE__ */ L("div", {
370
- className: "overflow-x-auto",
371
- children: /* @__PURE__ */ L("div", {
372
- className: "inline-flex items-center rounded-lg border border-border-subtle bg-bg-sunken/50 p-0.5",
373
- role: "tablist",
374
- "aria-label": "Filter recommendations by state",
375
- children: te.map((e) => {
376
- let t = e.key === _, n = F[e.key] ?? 0;
377
- return /* @__PURE__ */ R("button", {
378
- type: "button",
379
- role: "tab",
380
- "aria-selected": t,
381
- onClick: () => v(e.key),
382
- className: t ? "flex items-center gap-1.5 rounded-md bg-bg-surface px-3 py-1.5 text-sm font-medium text-text-primary shadow-sm" : "flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary",
383
- children: [e.label, /* @__PURE__ */ L("span", {
384
- className: t ? "rounded-full bg-action-primary-bg/15 px-1.5 text-xs font-semibold text-primary" : "rounded-full bg-bg-sunken px-1.5 text-xs font-semibold text-text-secondary",
385
- children: n
386
- })]
387
- }, e.key);
388
- })
389
- })
390
- }),
391
- V.length === 0 ? /* @__PURE__ */ L(u, {
702
+ }), /* @__PURE__ */ z("div", { children: Ee })]
703
+ }) : /* @__PURE__ */ B("div", {
704
+ className: "grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]",
705
+ children: [/* @__PURE__ */ B("div", { children: [/* @__PURE__ */ z(p, {
392
706
  illustration: "empty-data",
393
- title: $.title,
394
- description: $.description,
395
- action: _ === B.New ? X : void 0
396
- }) : /* @__PURE__ */ L("div", {
397
- className: "space-y-4",
398
- children: V.map((e) => /* @__PURE__ */ L(re, {
399
- rec: e,
400
- busy: b === e.id,
401
- onAccept: K,
402
- onSnooze: q,
403
- onDismiss: J
404
- }, e.id))
405
- }),
406
- Q
407
- ]
408
- }) : /* @__PURE__ */ R(I, { children: [/* @__PURE__ */ L(u, {
409
- illustration: "empty-data",
410
- title: "No recommendations yet",
411
- description: "TimeCampus turns your real timeline into gentle, values-aware suggestions — like protecting a focus block or reclaiming a commute. Generate your first set to see what it notices.",
412
- action: X
413
- }), Q] }), /* @__PURE__ */ L(d, {
414
- toasts: f,
415
- onDismiss: g
416
- })]
707
+ title: "No recommendations yet",
708
+ description: "TimeCampus turns your real timeline into gentle, values-aware suggestions — like protecting a focus block or reclaiming a commute. Generate your first set to see what it notices.",
709
+ action: Q
710
+ }), De] }), /* @__PURE__ */ z("div", { children: Ee })]
711
+ }),
712
+ /* @__PURE__ */ B(m, {
713
+ open: T !== null,
714
+ onClose: () => E(null),
715
+ title: "Dismiss this suggestion",
716
+ description: T?.title,
717
+ size: "sm",
718
+ footer: /* @__PURE__ */ B(de, { children: [/* @__PURE__ */ z("button", {
719
+ type: "button",
720
+ onClick: () => E(null),
721
+ className: Y,
722
+ children: "Cancel"
723
+ }), /* @__PURE__ */ B("button", {
724
+ type: "button",
725
+ onClick: () => void Se(),
726
+ disabled: ee !== null,
727
+ className: J,
728
+ children: [/* @__PURE__ */ z(R, { className: "h-4 w-4" }), "Dismiss"]
729
+ })] }),
730
+ children: [
731
+ /* @__PURE__ */ B("label", {
732
+ htmlFor: "rec-dismiss-reason",
733
+ className: "block text-sm font-medium text-text-primary",
734
+ children: [
735
+ "Why isn't this one for you?",
736
+ " ",
737
+ /* @__PURE__ */ z("span", {
738
+ className: "font-normal text-text-secondary",
739
+ children: "(optional)"
740
+ })
741
+ ]
742
+ }),
743
+ /* @__PURE__ */ z("textarea", {
744
+ id: "rec-dismiss-reason",
745
+ rows: 3,
746
+ value: D,
747
+ onChange: (e) => O(e.target.value),
748
+ placeholder: "e.g. already handled, not the right week",
749
+ className: "mt-1.5 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary"
750
+ }),
751
+ /* @__PURE__ */ z("p", {
752
+ className: "mt-2 text-xs text-text-secondary",
753
+ children: "Saved with the suggestion so you can see why you passed on it later. To stop this kind of suggestion entirely, use Mute this kind."
754
+ })
755
+ ]
756
+ }),
757
+ /* @__PURE__ */ z(h, {
758
+ toasts: f,
759
+ onDismiss: v
760
+ })
761
+ ]
417
762
  });
418
763
  }
419
764
  //#endregion
420
- export { Q as RecommendationsPage };
765
+ export { xe as RecommendationsPage };
421
766
 
422
767
  //# sourceMappingURL=RecommendationsPage.js.map