@kky42/pievo-server 2.4.11 → 2.4.13

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 (86) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/SignIn-CDCdDD1a.js +1 -0
  3. package/.output/public/assets/{ui-s5IMg6RF.js → actionUi-D8m1FDHO.js} +1 -1
  4. package/.output/public/assets/app-PPhgN9iN.css +2 -0
  5. package/.output/public/assets/index-c0XPWXuz.js +10 -0
  6. package/.output/public/assets/loops._loopId-D7i_BGiE.js +67 -0
  7. package/.output/public/assets/loops._loopId_.runs._runId-CksSrEY7.js +3 -0
  8. package/.output/public/assets/{preload-helper-Bs7oCAsw.js → preload-helper-Zcmr31XG.js} +3 -3
  9. package/.output/public/assets/routes-79D939J4.js +1 -0
  10. package/.output/public/assets/routes-BYgl3Tmv.js +1 -0
  11. package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
  12. package/.output/server/_ssr/{MachinesModal-BhRw7oOx.mjs → MachinesModal-DBIP648Z.mjs} +18 -57
  13. package/.output/server/_ssr/{SignIn-BhoZIOUp.mjs → SignIn-Bp3uWaCQ.mjs} +3 -3
  14. package/.output/server/_ssr/{ui-DGz-q9MT.mjs → actionUi-C_WswNb1.mjs} +113 -2
  15. package/.output/server/_ssr/{artifactFiles-ChuJ4PzC.mjs → artifactFiles-BvVmixrs.mjs} +3 -3
  16. package/.output/server/_ssr/{artifactKind-DXumOv7c.mjs → artifactKind-DRCbYHZU.mjs} +6 -14
  17. package/.output/server/_ssr/artifacts-CzDJLaa6.mjs +2 -0
  18. package/.output/server/_ssr/{auth-LwPp_iim.mjs → auth-BV0MxVdo.mjs} +44 -124
  19. package/.output/server/_ssr/{SocialLinks-DVCLmql0.mjs → auth-client-RTpMpT4L.mjs} +143 -109
  20. package/.output/server/_ssr/boot-CsLXrLEa.mjs +2 -0
  21. package/.output/server/_ssr/{boot-BIxtZHU-.mjs → boot-U-CFI65B.mjs} +58 -331
  22. package/.output/server/_ssr/{bun-sqlite-dialect-BW9W1_Ps-CHMDkKqA.mjs → bun-sqlite-dialect-BW9W1_Ps-D9KUBJ-L.mjs} +2 -2
  23. package/.output/server/_ssr/{d1-sqlite-dialect-BLC8LXE6-DYum6qky.mjs → d1-sqlite-dialect-BLC8LXE6-CVHRzH1n.mjs} +2 -2
  24. package/.output/server/_ssr/db-BTX-xORB.mjs +2 -0
  25. package/.output/server/_ssr/{dist-bWqs5sTy.mjs → dist-D7UDVZ5G.mjs} +2 -2
  26. package/.output/server/_ssr/{factory-pBwrjVTI.mjs → factory-DYFx1fg-.mjs} +2 -2
  27. package/.output/server/_ssr/{kysely-adapter-DMf5pFTf.mjs → kysely-adapter-CwdB7yBh.mjs} +1 -1
  28. package/.output/server/_ssr/logger-CwiKSMp6.mjs +2 -0
  29. package/.output/server/_ssr/{loopApi-Im6JSK0p.mjs → loopApi-C4nN0msL.mjs} +29 -90
  30. package/.output/server/_ssr/{loopProjection-DIpGR6rp.mjs → loopProjection-DMSNHux6.mjs} +2 -2
  31. package/.output/server/_ssr/{loops._loopId-B5iPseDG.mjs → loops._loopId-Q4pRYPip.mjs} +19 -45
  32. package/.output/server/_ssr/{loops._loopId_.runs._runId-DVtG0xYb.mjs → loops._loopId_.runs._runId-DNXVmCt5.mjs} +6 -13
  33. package/.output/server/_ssr/{loops._loopId_.runs._runId-BA-P6sGA.mjs → loops._loopId_.runs._runId-ty3AhkOi.mjs} +31 -23
  34. package/.output/server/_ssr/{machineFns-Bb9KNXun.mjs → machineFns-CkNms3zA.mjs} +41 -108
  35. package/.output/server/_ssr/{node-sqlite-dialect-Dho9YjXb.mjs → node-sqlite-dialect-B_g92oFK.mjs} +2 -2
  36. package/.output/server/_ssr/{router-DPz3uoUr.mjs → router-DCNZ2xLm.mjs} +26 -40
  37. package/.output/server/_ssr/routes-8JSF7bX_.mjs +27 -0
  38. package/.output/server/_ssr/{routes-FO0MFfYK.mjs → routes-B3fulW5I.mjs} +4 -9
  39. package/.output/server/_ssr/routes-BaanDO41.mjs +367 -0
  40. package/.output/server/_ssr/runDiff-D7CxAgYL.mjs +2 -0
  41. package/.output/server/_ssr/ssr.mjs +28 -88
  42. package/.output/server/_ssr/store-DSNbXxqz.mjs +2 -0
  43. package/.output/server/_ssr/tokens-DzUv7CCp.mjs +2 -0
  44. package/.output/server/_tanstack-start-manifest_v-DkmxsHDK.mjs +52 -0
  45. package/.output/server/drizzle/0002_remove_teams.sql +15 -0
  46. package/.output/server/drizzle/meta/_journal.json +7 -0
  47. package/.output/server/index.mjs +49 -98
  48. package/drizzle/0002_remove_teams.sql +15 -0
  49. package/drizzle/meta/_journal.json +7 -0
  50. package/package.json +2 -2
  51. package/.output/public/assets/MachinesModal-ByHejech.js +0 -1
  52. package/.output/public/assets/SignIn-Cf4HOpdF.js +0 -1
  53. package/.output/public/assets/actionUi-yYVvoOTn.js +0 -1
  54. package/.output/public/assets/app-IrlFuBXa.css +0 -2
  55. package/.output/public/assets/index-ia26HsP1.js +0 -10
  56. package/.output/public/assets/invite._token-B0EKVyrU.js +0 -1
  57. package/.output/public/assets/invite._token-tDfe3p0Z.js +0 -1
  58. package/.output/public/assets/lifecycleUi-C0DaC6NM.js +0 -1
  59. package/.output/public/assets/loops._loopId-CuYq_XgT.js +0 -67
  60. package/.output/public/assets/loops._loopId_.runs._runId-mOaF51Qu.js +0 -3
  61. package/.output/public/assets/routes-4N6mrQK_.js +0 -1
  62. package/.output/public/assets/routes-Dbl_3lt_.js +0 -1
  63. package/.output/public/assets/t._teamId-BVsl0EGm.js +0 -1
  64. package/.output/public/assets/t._teamId-CAUjvMZ6.js +0 -1
  65. package/.output/server/_ssr/DashboardView-BMHMrOfC.mjs +0 -842
  66. package/.output/server/_ssr/actionUi-Cf6DuEC2.mjs +0 -119
  67. package/.output/server/_ssr/artifacts-DOLHJHcd.mjs +0 -2
  68. package/.output/server/_ssr/boot-D8cj4uZj.mjs +0 -2
  69. package/.output/server/_ssr/db-BphSJs66.mjs +0 -2
  70. package/.output/server/_ssr/invite._token-CPSvdDox.mjs +0 -27
  71. package/.output/server/_ssr/invite._token-CRBsTSTZ.mjs +0 -84
  72. package/.output/server/_ssr/invite._token-CaTLt92a.mjs +0 -20
  73. package/.output/server/_ssr/lifecycleUi-DauKavgx.mjs +0 -50
  74. package/.output/server/_ssr/logger-EinA2J6y.mjs +0 -2
  75. package/.output/server/_ssr/routes-B9zD2h4Y.mjs +0 -26
  76. package/.output/server/_ssr/routes-BuILEmHs.mjs +0 -39
  77. package/.output/server/_ssr/runDiff-BLdot9mi.mjs +0 -2
  78. package/.output/server/_ssr/store-CFCp_ggC.mjs +0 -2
  79. package/.output/server/_ssr/t._teamId-1t5wOXrV.mjs +0 -43
  80. package/.output/server/_ssr/t._teamId-A37pQ8Vp.mjs +0 -32
  81. package/.output/server/_ssr/t._teamId-D1BTwhWf.mjs +0 -31
  82. package/.output/server/_ssr/teamCookie-B7hLOIie.mjs +0 -10
  83. package/.output/server/_ssr/teamFns-DfqtuBW2.mjs +0 -451
  84. package/.output/server/_ssr/teamFns-cI_tsiCv.mjs +0 -32
  85. package/.output/server/_ssr/tokens-CUz4LDLm.mjs +0 -2
  86. package/.output/server/_tanstack-start-manifest_v-CojB4Veh.mjs +0 -86
@@ -1,64 +1,25 @@
1
1
  import { o as __toESM } from "../_runtime.mjs";
2
- import { tt as isOutdated, xt as machinePresence } from "./boot-BIxtZHU-.mjs";
2
+ import { G as isOutdated, ct as machinePresence } from "./boot-U-CFI65B.mjs";
3
3
  import { a as DialogClose, c as TooltipPopup, d as TooltipTrigger, f as TooltipRoot, g as require_react, h as require_jsx_runtime, i as DialogPopup, l as TooltipPositioner, n as DialogRoot, o as DialogBackdrop, r as DialogPortal, t as DialogTitle, u as TooltipPortal } from "../_libs/@base-ui/react+[...].mjs";
4
4
  import { n as createServerFn } from "./ssr.mjs";
5
- import { d as getConfig, o as createSsrRpc } from "./SocialLinks-DVCLmql0.mjs";
6
- import { C as scheduleText, D as until, O as useHydrated, _ as inputCls, a as btn, b as rel, d as dotLabel, f as dotOpacity, h as fnum, m as fmt, o as btnDanger, p as dur, r as Pill, s as btnPrimary, t as ErrorBanner, u as dotColor, v as labelCls, w as sectionHeadCls, x as runPulseAnim, y as lastRunOf } from "./ui-DGz-q9MT.mjs";
7
- import { t as ConfirmBar } from "./actionUi-Cf6DuEC2.mjs";
8
- import { i as lifecyclePresentation, n as daemonStopSupport } from "./lifecycleUi-DauKavgx.mjs";
9
- //#region node_modules/.nitro/vite/services/ssr/assets/MachinesModal-BhRw7oOx.js
5
+ import { g as lifecyclePresentation, o as createSsrRpc, p as getConfig, s as daemonStopSupport } from "./auth-client-RTpMpT4L.mjs";
6
+ import { A as until, C as rel, D as sectionHeadCls, E as scheduleText, M as useHydrated, S as lastRunOf, _ as fmt, b as inputCls, c as btn, g as dur, h as dotOpacity, l as btnDanger, m as dotLabel, n as ErrorBanner, o as Pill, p as dotColor, t as ConfirmBar, u as btnPrimary, v as fnum, w as runPulseAnim, x as labelCls } from "./actionUi-C_WswNb1.mjs";
7
+ //#region node_modules/.nitro/vite/services/ssr/assets/MachinesModal-DBIP648Z.js
10
8
  var import_react = /* @__PURE__ */ __toESM(require_react());
11
9
  var import_jsx_runtime = require_jsx_runtime();
12
10
  /**
13
- * Machine management server functions. Connect flow (two acts):
14
- * 1. createMachine() → mint a token, create a PENDING (unnamed) row, return
15
- * {id, token}. The UI shows the connect command + "waiting…".
16
- * 2. the daemon connects → poll records online + hostname/platform/arch. The
17
- * UI polls machineStatus(id); once connected it shows the name field
18
- * (prefilled with the hostname). finalizeMachine({id,name}) names it →
19
- * it appears in the list. cancelMachine(id) drops a pending row.
20
- *
21
- * Scoping: when the auth gate is on, every fn requires a signed-in user, and a
22
- * machine is visible/actionable only when the requester owns it, its owner is a
23
- * member of the active team, or the scope is the admin "All teams" view (the
24
- * same rule listMachines filters by). The PLAINTEXT device token is serialized
25
- * ONLY to the machine's owner — it fully impersonates the machine (poll /
26
- * create-loop / log), so a teammate who can see the row must never exfiltrate
27
- * it. Open mode (gate off) keeps the shared workspace: everyone sees/manages
28
- * all machines, tokens included. No workdir jail (fully open) for now.
11
+ * Machine-management server functions. In auth mode, the signed-in user is the
12
+ * sole human authorization boundary. Open mode intentionally shares all machine
13
+ * administration, including reconnect tokens.
29
14
  */
30
- /** Ids of the machines membership-visible in the scope's active team (the same
31
- * set listMachines serves). Empty when the scope needs no team check (open
32
- * mode or signed-out — machineInScope settles those before ever invoking its
33
- * team-set thunk). */
34
- /**
35
- * Resolve a machine and authorize it against the request scope (the machine-fn
36
- * twin of loopApi's `ownedLoop`). `machine` is undefined when it's missing OR
37
- * out of scope — callers treat both as "not found" so existence never leaks.
38
- * The scope rides along so callers don't re-run requestScope (a second session
39
- * decrypt); the team-set thunk keeps the owner fast path query-free.
40
- */
41
- /** Named machines only (pending/unnamed rows are mid-connect). Scoped to the
42
- * given/active team when the gate is on; the full shared list in open mode. An
43
- * explicit `teamId` (the `/t/<id>` route) scopes this request independent of the
44
- * cookie, so a tab on /t/A and one on /t/B list different machines at once. */
45
- var listMachines = createServerFn({ method: "GET" }).validator((teamId) => teamId).handler(createSsrRpc("aa46310016bb8a58b273a648ffd498c87f1f450d12827e30110a3b83ba6a27d9"));
46
- /** Act 1 — create a pending machine + token, owned by the signed-in user's team.
47
- * An explicit `teamId` (the `/t/<id>` route) binds the machine to that tab's team
48
- * independent of the last-used cookie, mirroring `mintClaim`/`listMachines`. */
49
- var createMachine = createServerFn({ method: "POST" }).validator((teamId) => teamId).handler(createSsrRpc("ed679b4e1e05e903a5d4ba4a70d9b9d883991ab22ed59b4d2d69078941818415"));
50
- /** Poll while the connect dialog is open. Scoped like listMachines; the token
51
- * rides along only for the machine's owner (the connect dialog's minter). */
15
+ /** Missing and out-of-scope machines both resolve to undefined. */
16
+ var listMachines = createServerFn({ method: "GET" }).handler(createSsrRpc("aa46310016bb8a58b273a648ffd498c87f1f450d12827e30110a3b83ba6a27d9"));
17
+ var createMachine = createServerFn({ method: "POST" }).handler(createSsrRpc("ed679b4e1e05e903a5d4ba4a70d9b9d883991ab22ed59b4d2d69078941818415"));
52
18
  var machineStatus = createServerFn({ method: "GET" }).validator((id) => id).handler(createSsrRpc("169566e7637a22599b4eb32ee3775574e99677406922bbef7482a102036c33dd"));
53
- var finalizeMachine = createServerFn({ method: "POST" }).validator((d) => d).handler(createSsrRpc("27d7eba0b6a2257949392fdd56057275a5c5366484ae01faf3b5897a225ecd83"));
19
+ var finalizeMachine = createServerFn({ method: "POST" }).validator((data) => data).handler(createSsrRpc("27d7eba0b6a2257949392fdd56057275a5c5366484ae01faf3b5897a225ecd83"));
54
20
  /**
55
- * Cancel a pending connect or delete a machine and all of its server-owned data.
56
- * Each loop goes through forceDeleteLoop so execution authority is retired and
57
- * history/artifact metadata is removed. Nothing reaches into the machine's cwd.
58
- *
59
- * A teammate may still remove an empty machine as before. Cascading loops is
60
- * stronger: under the auth gate the caller must own every affected loop team,
61
- * matching the authority required to force-retire those teams' run leases.
21
+ * Delete server-owned machine data without touching local project files. The
22
+ * store rechecks ownership under the deletion lock in auth mode.
62
23
  */
63
24
  var deleteMachine = createServerFn({ method: "POST" }).validator((id) => id).handler(createSsrRpc("a6384dc7154d9930422d29396f5de64b3ac69c62ad3824bc4a22d745fc07b293"));
64
25
  /** Merge two chronological (oldest-first) run lists, dedup by id (the first
@@ -403,7 +364,7 @@ function machineOrder(a, b) {
403
364
  sensitivity: "base"
404
365
  }) || a.id.localeCompare(b.id);
405
366
  }
406
- function MachinesModal({ open, onClose, teamId }) {
367
+ function MachinesModal({ open, onClose }) {
407
368
  const [machines, setMachines] = (0, import_react.useState)([]);
408
369
  const [pending, setPending] = (0, import_react.useState)(null);
409
370
  const [status, setStatus] = (0, import_react.useState)(null);
@@ -435,10 +396,10 @@ function MachinesModal({ open, onClose, teamId }) {
435
396
  const load = (0, import_react.useCallback)(async () => {
436
397
  const request = ++loadRequest.current;
437
398
  try {
438
- const next = await listMachines({ data: teamId });
399
+ const next = await listMachines();
439
400
  if (request === loadRequest.current) setMachines([...next].sort(machineOrder));
440
401
  } catch {}
441
- }, [teamId]);
402
+ }, []);
442
403
  const openRef = (0, import_react.useRef)(open);
443
404
  openRef.current = open;
444
405
  (0, import_react.useEffect)(() => {
@@ -474,7 +435,7 @@ function MachinesModal({ open, onClose, teamId }) {
474
435
  setBusy(true);
475
436
  setDelErr(null);
476
437
  try {
477
- const r = await createMachine({ data: teamId });
438
+ const r = await createMachine();
478
439
  if ("error" in r) {
479
440
  setDelErr(r.error);
480
441
  return;
@@ -756,4 +717,4 @@ function MachinesModal({ open, onClose, teamId }) {
756
717
  });
757
718
  }
758
719
  //#endregion
759
- export { ModalSection as a, listMachines as c, ModalHead as i, mergeRuns as l, MachinesModal as n, Timeline as o, Modal as r, daemonConnectCommand as s, LoopOverview as t };
720
+ export { Timeline as a, mergeRuns as c, ModalHead as i, MachinesModal as n, daemonConnectCommand as o, Modal as r, listMachines as s, LoopOverview as t };
@@ -1,7 +1,7 @@
1
1
  import { h as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
2
- import { S as signIn, n as GitHubIcon, r as PievoLogo, t as GITHUB_URL } from "./SocialLinks-DVCLmql0.mjs";
3
- import { s as btnPrimary } from "./ui-DGz-q9MT.mjs";
4
- //#region node_modules/.nitro/vite/services/ssr/assets/SignIn-BhoZIOUp.js
2
+ import { C as signIn, a as PievoLogo, n as GITHUB_URL, r as GitHubIcon } from "./auth-client-RTpMpT4L.mjs";
3
+ import { u as btnPrimary } from "./actionUi-C_WswNb1.mjs";
4
+ //#region node_modules/.nitro/vite/services/ssr/assets/SignIn-Bp3uWaCQ.js
5
5
  var import_jsx_runtime = require_jsx_runtime();
6
6
  /**
7
7
  * Sign-in CTA — shown by the auth gate when a GitHub OAuth app is configured and
@@ -1,6 +1,6 @@
1
1
  import { o as __toESM } from "../_runtime.mjs";
2
2
  import { g as require_react, h as require_jsx_runtime } from "../_libs/@base-ui/react+[...].mjs";
3
- //#region node_modules/.nitro/vite/services/ssr/assets/ui-DGz-q9MT.js
3
+ //#region node_modules/.nitro/vite/services/ssr/assets/actionUi-C_WswNb1.js
4
4
  var import_react = /* @__PURE__ */ __toESM(require_react());
5
5
  var import_jsx_runtime = require_jsx_runtime();
6
6
  var fmt = (t) => t ? new Date(t).toLocaleString() : "—";
@@ -242,5 +242,116 @@ function StatusPill({ run, colorText }) {
242
242
  }), dotLabel(run)]
243
243
  });
244
244
  }
245
+ /**
246
+ * First-load failure card — the calm "couldn't load X" + Retry the dashboard,
247
+ * loop page, and run page all show instead of a raw error dump. One shared
248
+ * markup so the three screens can't drift.
249
+ */
250
+ function LoadErrorCard({ title, detail, onRetry }) {
251
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
252
+ className: "rounded-card border border-hairline bg-surface px-6 py-8 shadow-card",
253
+ children: [
254
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
255
+ className: "text-[14px] text-accent",
256
+ children: title
257
+ }),
258
+ detail && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
259
+ className: "mt-1 text-meta text-secondary",
260
+ children: detail
261
+ }),
262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
263
+ type: "button",
264
+ onClick: onRetry,
265
+ className: "mt-3 cursor-pointer border-none bg-transparent p-0 text-label font-medium text-interactive underline underline-offset-2 hover:text-display",
266
+ children: "Retry"
267
+ })
268
+ ]
269
+ });
270
+ }
271
+ /**
272
+ * In-panel guard — the Nothing-styled stand-in for native confirm(). Owns its
273
+ * own a11y so the caller just renders it when armed: focus lands on the CTA,
274
+ * Esc cancels, Enter (CTA focused) confirms. Render with `key={kind}` so it
275
+ * remounts (and re-focuses) per guard.
276
+ */
277
+ function ConfirmBar({ prompt, note, cta, danger, busy, onConfirm, onCancel }) {
278
+ const ctaRef = (0, import_react.useRef)(null);
279
+ (0, import_react.useEffect)(() => {
280
+ ctaRef.current?.focus();
281
+ }, []);
282
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
283
+ role: "group",
284
+ "aria-label": prompt,
285
+ onKeyDown: (e) => {
286
+ if (e.key === "Escape" && !busy) onCancel();
287
+ },
288
+ className: "flex flex-wrap items-center gap-x-4 gap-y-2.5 rounded-control border border-hairline bg-raised px-4 py-3",
289
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
290
+ className: "min-w-0",
291
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
292
+ className: "text-body font-medium text-display",
293
+ children: prompt
294
+ }), note && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
295
+ className: "mt-0.5 text-meta text-secondary",
296
+ children: note
297
+ })]
298
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
299
+ className: "ml-auto flex items-center gap-2.5",
300
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
301
+ ref: ctaRef,
302
+ className: danger ? btnDanger : btnPrimary,
303
+ disabled: busy,
304
+ onClick: onConfirm,
305
+ children: busy ? "Working…" : cta
306
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
307
+ className: btn,
308
+ disabled: busy,
309
+ onClick: onCancel,
310
+ children: "Cancel"
311
+ })]
312
+ })]
313
+ });
314
+ }
315
+ /**
316
+ * Transient peak-end acknowledgement (✓ done / ✕ gone), announced to AT via
317
+ * aria-live. Self-clearing is the caller's concern (see `useFlash`); this is
318
+ * pure presentation. No outer margin — wrap at the call site for spacing.
319
+ */
320
+ function FlashLine({ label, tone = "ok", onUndo }) {
321
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
322
+ role: "status",
323
+ "aria-live": "polite",
324
+ className: "inline-flex items-center gap-2 text-label font-medium text-secondary",
325
+ style: { animation: "fadeIn 0.2s ease-out" },
326
+ children: [
327
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
328
+ "aria-hidden": true,
329
+ className: tone === "gone" ? "text-accent" : "text-success",
330
+ children: tone === "gone" ? "✕" : "✓"
331
+ }),
332
+ label,
333
+ onUndo && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
334
+ type: "button",
335
+ onClick: onUndo,
336
+ className: "cursor-pointer border-none bg-transparent p-0 text-label font-medium text-interactive underline underline-offset-2 transition-colors hover:text-display",
337
+ children: "Undo"
338
+ })
339
+ ]
340
+ });
341
+ }
342
+ /**
343
+ * Flash state + its self-clear timer in one unit. `hold` wins; else an Undo
344
+ * flash lingers ~6s (reachable), a bare "done" fades after ~2.2s. Fade, don't
345
+ * slide.
346
+ */
347
+ function useFlash() {
348
+ const [flash, setFlash] = (0, import_react.useState)(null);
349
+ (0, import_react.useEffect)(() => {
350
+ if (!flash) return;
351
+ const t = setTimeout(() => setFlash(null), flash.hold ?? (flash.undo ? 6e3 : 2200));
352
+ return () => clearTimeout(t);
353
+ }, [flash]);
354
+ return [flash, setFlash];
355
+ }
245
356
  //#endregion
246
- export { scheduleText as C, until as D, tsShort as E, useHydrated as O, runPulseStyle as S, selectCls as T, inputCls as _, btn as a, rel as b, btnQuiet as c, dotLabel as d, dotOpacity as f, humanBytes as g, fnum as h, StatusPill as i, btnSm as l, fmt as m, Loading as n, btnDanger as o, dur as p, Pill as r, btnPrimary as s, ErrorBanner as t, dotColor as u, labelCls as v, sectionHeadCls as w, runPulseAnim as x, lastRunOf as y };
357
+ export { until as A, rel as C, sectionHeadCls as D, scheduleText as E, useHydrated as M, selectCls as O, lastRunOf as S, runPulseStyle as T, fmt as _, Loading as a, inputCls as b, btn as c, btnQuiet as d, btnSm as f, dur as g, dotOpacity as h, LoadErrorCard as i, useFlash as j, tsShort as k, btnDanger as l, dotLabel as m, ErrorBanner as n, Pill as o, dotColor as p, FlashLine as r, StatusPill as s, ConfirmBar as t, btnPrimary as u, fnum as v, runPulseAnim as w, labelCls as x, humanBytes as y };
@@ -1,6 +1,6 @@
1
- import { F as getArtifactFile, I as getArtifactSync, Lt as safeRelPath, lt as listArtifacts } from "./boot-BIxtZHU-.mjs";
2
- import { n as toArtifactSummary } from "./loopProjection-DIpGR6rp.mjs";
3
- //#region node_modules/.nitro/vite/services/ssr/assets/artifactFiles-ChuJ4PzC.js
1
+ import { O as getArtifactFile, Z as listArtifacts, k as getArtifactSync, xt as safeRelPath } from "./boot-U-CFI65B.mjs";
2
+ import { n as toArtifactSummary } from "./loopProjection-DMSNHux6.mjs";
3
+ //#region node_modules/.nitro/vite/services/ssr/assets/artifactFiles-BvVmixrs.js
4
4
  /**
5
5
  * Server-side reads over a loop's current exact artifacts. Pure helpers
6
6
  * (no request/session context) so they're shared by the lazy server fns
@@ -1,27 +1,19 @@
1
1
  import { c as createFileRoute, s as lazyRouteComponent } from "../_libs/@tanstack/react-router+[...].mjs";
2
- import { i as authClient, u as getAuthState } from "./SocialLinks-DVCLmql0.mjs";
3
- //#region node_modules/.nitro/vite/services/ssr/assets/artifactKind-DXumOv7c.js
4
- var $$splitComponentImporter = () => import("./loops._loopId-B5iPseDG.mjs");
2
+ import { f as getAuthState } from "./auth-client-RTpMpT4L.mjs";
3
+ //#region node_modules/.nitro/vite/services/ssr/assets/artifactKind-DRCbYHZU.js
4
+ var $$splitComponentImporter = () => import("./loops._loopId-Q4pRYPip.mjs");
5
5
  /**
6
6
  * Loop detail page — `/loops/$loopId`: a loop header and action toolbar, the
7
7
  * exact artifact Files panel, and the runs timeline. The view owns its own
8
8
  * data + self-poll (ssr:false so the session cookie rides along with its fetches,
9
9
  * like the dashboard loader). Run rows link on to `/loops/$loopId/runs/$runId`.
10
10
  *
11
- * Auth-gated exactly like the dashboard (`/`): the loader checks getAuthState +
12
- * the session so a logged-out/expired DEEP LINK shows the sign-in CTA instead of
13
- * a raw `loop not found` error from the ownership-blocked data fetch.
11
+ * Auth-gated exactly like the dashboard (`/`): Better Auth's session hook keeps a
12
+ * logged-out or expired deep link from mounting the ownership-scoped data view.
14
13
  */
15
14
  var Route = createFileRoute("/loops/$loopId")({
16
15
  ssr: false,
17
- loader: async () => {
18
- const auth = await getAuthState();
19
- if (auth.enabled) {
20
- const { data: session } = await authClient.getSession();
21
- if (!session) return { auth };
22
- }
23
- return { auth };
24
- },
16
+ loader: async () => ({ auth: await getAuthState() }),
25
17
  component: lazyRouteComponent($$splitComponentImporter, "component")
26
18
  });
27
19
  /** Extension → MIME for the image types we serve inline. This is an ALLOWLIST:
@@ -0,0 +1,2 @@
1
+ import { dt as parseArtifactSyncBody, f as SYNC_BODY_CAP, i as BLOB_CAP } from "./boot-U-CFI65B.mjs";
2
+ export { BLOB_CAP, SYNC_BODY_CAP, parseArtifactSyncBody };
@@ -1,6 +1,6 @@
1
1
  import { C as notInArray, S as ne, _ as isNotNull, b as lt, d as and, f as eq, g as inArray, h as ilike, j as sql, l as asc, m as gte, p as gt, s as count, u as desc, v as isNull$1, w as or, x as lte, y as like } from "../_libs/drizzle-orm+postgres.mjs";
2
- import { $ as isDevelopment, B as getEnvVar, Ct as normalizePathname, D as defu, E as db, H as getHost, It as resolveBaseURL, K as getOrigin, L as getBaseURL, M as env, R as getBooleanEnvVar, Wt as teamIdForUser, a as BetterAuthError, b as createDefu, c as ENV, ct as kAPIErrorHeaderSymbol, en as wildcardMatch, et as isDynamicBaseURLConfig, g as capitalizeFirstLetter, h as betterFetch, it as isRequestLike, j as ensureTeam, n as APIError$1, o as BetterCallError, ot as isTeamMember, p as ValidationError$1, q as getProtocol, r as BASE_ERROR_CODES, rt as isProduction, st as isTest, t as APIError } from "./boot-BIxtZHU-.mjs";
3
- import { C as withSpan, S as shouldPublishLog, _ as logger, a as __getBetterAuthGlobal, b as runWithTransaction, c as createRandomStringGenerator, d as getAuthTables, f as getBetterAuthVersion, g as initGetModelName, h as initGetFieldName, l as generateId$2, m as import_src, n as ATTR_HOOK_TYPE, o as createAdapterFactory, p as getCurrentAdapter, r as ATTR_OPERATION_ID, s as createLogger, t as ATTR_CONTEXT, u as getAsyncLocalStorage, v as queueAfterTransactionHook, x as safeJSONParse, y as runWithAdapter } from "./factory-pBwrjVTI.mjs";
2
+ import { A as getBaseURL, C as defu, F as getHost, Ft as wildcardMatch, K as isProduction, N as getEnvVar, R as getOrigin, S as db, T as env, U as isDevelopment, W as isDynamicBaseURLConfig, X as kAPIErrorHeaderSymbol, Y as isTest, a as BetterAuthError, at as loginGateEnabled, bt as resolveBaseURL, c as ENV, h as capitalizeFirstLetter, j as getBooleanEnvVar, m as betterFetch, n as APIError$1, o as BetterCallError, p as ValidationError$1, q as isRequestLike, r as BASE_ERROR_CODES, t as APIError, ut as normalizePathname, v as createDefu, z as getProtocol } from "./boot-U-CFI65B.mjs";
3
+ import { C as withSpan, S as shouldPublishLog, _ as logger, a as __getBetterAuthGlobal, b as runWithTransaction, c as createRandomStringGenerator, d as getAuthTables, f as getBetterAuthVersion, g as initGetModelName, h as initGetFieldName, l as generateId$2, m as import_src, n as ATTR_HOOK_TYPE, o as createAdapterFactory, p as getCurrentAdapter, r as ATTR_OPERATION_ID, s as createLogger, t as ATTR_CONTEXT, u as getAsyncLocalStorage, v as queueAfterTransactionHook, x as safeJSONParse, y as runWithAdapter } from "./factory-DYFx1fg-.mjs";
4
4
  import { randomBytes, scrypt } from "node:crypto";
5
5
  import fs from "node:fs";
6
6
  import path from "node:path";
@@ -1700,7 +1700,7 @@ var JOIN_TYPE_SQL = freeze({
1700
1700
  Using: "using"
1701
1701
  });
1702
1702
  //#endregion
1703
- //#region node_modules/.nitro/vite/services/ssr/assets/auth-LwPp_iim.js
1703
+ //#region node_modules/.nitro/vite/services/ssr/assets/auth-BV0MxVdo.js
1704
1704
  /**
1705
1705
  * @internal
1706
1706
  */
@@ -15993,7 +15993,7 @@ var createKyselyAdapter = async (config) => {
15993
15993
  if ("getConnection" in db) dialect = new MysqlDialect(db);
15994
15994
  if ("connect" in db) dialect = new PostgresDialect({ pool: db });
15995
15995
  if ("fileControl" in db) {
15996
- const { BunSqliteDialect } = await import("./bun-sqlite-dialect-BW9W1_Ps-CHMDkKqA.mjs");
15996
+ const { BunSqliteDialect } = await import("./bun-sqlite-dialect-BW9W1_Ps-D9KUBJ-L.mjs");
15997
15997
  dialect = new BunSqliteDialect({ database: db });
15998
15998
  }
15999
15999
  if ("createSession" in db) {
@@ -16009,12 +16009,12 @@ var createKyselyAdapter = async (config) => {
16009
16009
  if (error !== null && typeof error === "object" && "code" in error && error.code !== "ERR_UNKNOWN_BUILTIN_MODULE") throw error;
16010
16010
  }
16011
16011
  if (DatabaseSync && db instanceof DatabaseSync) {
16012
- const { NodeSqliteDialect } = await import("./node-sqlite-dialect-Dho9YjXb.mjs");
16012
+ const { NodeSqliteDialect } = await import("./node-sqlite-dialect-B_g92oFK.mjs");
16013
16013
  dialect = new NodeSqliteDialect({ database: db });
16014
16014
  }
16015
16015
  }
16016
16016
  if ("batch" in db && "exec" in db && "prepare" in db) {
16017
- const { D1SqliteDialect } = await import("./d1-sqlite-dialect-BLC8LXE6-DYum6qky.mjs");
16017
+ const { D1SqliteDialect } = await import("./d1-sqlite-dialect-BLC8LXE6-CVHRzH1n.mjs");
16018
16018
  dialect = new D1SqliteDialect({ database: db });
16019
16019
  }
16020
16020
  return {
@@ -36526,7 +36526,7 @@ async function getBaseAdapter(options, handleDirectDatabase) {
36526
36526
  acc[key] = [];
36527
36527
  return acc;
36528
36528
  }, {});
36529
- const { memoryAdapter } = await import("./dist-bWqs5sTy.mjs");
36529
+ const { memoryAdapter } = await import("./dist-D7UDVZ5G.mjs");
36530
36530
  adapter = memoryAdapter(memoryDB)(options);
36531
36531
  } else if (typeof options.database === "function") adapter = options.database(options);
36532
36532
  else adapter = await handleDirectDatabase(options);
@@ -36540,10 +36540,10 @@ async function getBaseAdapter(options, handleDirectDatabase) {
36540
36540
  }
36541
36541
  async function getAdapter(options) {
36542
36542
  return getBaseAdapter(options, async (opts) => {
36543
- const { createKyselyAdapter } = await import("./kysely-adapter-DMf5pFTf.mjs");
36543
+ const { createKyselyAdapter } = await import("./kysely-adapter-CwdB7yBh.mjs");
36544
36544
  const { kysely, databaseType, transaction } = await createKyselyAdapter(opts);
36545
36545
  if (!kysely) throw new BetterAuthError("Failed to initialize database adapter");
36546
- const { kyselyAdapter } = await import("./kysely-adapter-DMf5pFTf.mjs");
36546
+ const { kyselyAdapter } = await import("./kysely-adapter-CwdB7yBh.mjs");
36547
36547
  return kyselyAdapter(kysely, {
36548
36548
  type: databaseType || "sqlite",
36549
36549
  debugLogs: opts.database && "debugLogs" in opts.database ? opts.database.debugLogs : false,
@@ -38278,40 +38278,23 @@ var drizzleAdapter = (db, config) => {
38278
38278
  };
38279
38279
  };
38280
38280
  /**
38281
- * The GitHub login-gate condition, in ONE place. `auth.ts` derives its load-time
38282
- * `authEnabled` const from this; the machine-enrollment guard (`gateway/index.ts`
38283
- * `poll`) calls it LIVE so the gated-vs-open behavior can be exercised in a test
38284
- * (which sets the env vars per-case) without pulling betterAuth into the machine
38285
- * hot path. Leaf module — no imports, no side effects.
38286
- *
38287
- * The gate is ON exactly when a GitHub OAuth app is configured (both id + secret).
38288
- * OFF ⇒ open/dev mode (single shared workspace, anonymous self-registration).
38289
- */
38290
- function loginGateEnabled() {
38291
- return !!(process.env.GITHUB_CLIENT_ID?.trim() && process.env.GITHUB_CLIENT_SECRET?.trim());
38292
- }
38293
- /**
38294
- * Better Auth with GitHub social login. Supplying the GitHub OAuth credentials
38295
- * enables authentication; `PIEVO_ALLOWED_LOGINS` optionally narrows who may sign
38296
- * in. Authenticated data is team-scoped, while `userId` retains creator/owner
38297
- * attribution. With no GitHub credentials the local server runs in open mode.
38281
+ * Better Auth with GitHub social login. Supplying GitHub OAuth credentials
38282
+ * enables authentication; `PIEVO_ALLOWED_LOGINS` optionally narrows who may
38283
+ * sign in. Auth mode isolates data by signed-in user. Without GitHub
38284
+ * credentials, the server is one shared open-mode workspace.
38298
38285
  */
38299
38286
  var clientId = process.env.GITHUB_CLIENT_ID?.trim();
38300
38287
  var clientSecret = process.env.GITHUB_CLIENT_SECRET?.trim();
38301
- /** Auth is enforced only when a GitHub OAuth app is configured. Single source of
38302
- * the condition is `loginGateEnabled()` — the machine-enrollment guard reads it
38303
- * live so the two can never drift. */
38288
+ /** Keep the web-session gate and machine-enrollment gate on one live condition. */
38304
38289
  var authEnabled = loginGateEnabled();
38305
38290
  var authSecret = process.env.PIEVO_AUTH_SECRET?.trim();
38306
38291
  if (authEnabled && !authSecret) throw new Error("PIEVO_AUTH_SECRET must be set when the GitHub login gate is enabled (GITHUB_CLIENT_ID/SECRET present) — refusing to fall back to the public dev secret.");
38307
- var allowlist = (process.env.PIEVO_ALLOWED_LOGINS || "").split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
38308
38292
  /**
38309
- * Whether an email may sign in. An empty allowlist means "allow anyone" (open
38310
- * mode). Each allowlist entry is either a full email (exact match) or a DOMAIN
38311
- * WILDCARD — `@example.com` or `*@example.com` — matching any address at that
38312
- * domain (so `*@example.com` admits the whole team without listing each one).
38293
+ * An empty allowlist permits any GitHub-authenticated user. Entries may be full
38294
+ * email addresses or domain wildcards (`@example.com` or `*@example.com`).
38313
38295
  */
38314
38296
  function emailAllowed(email) {
38297
+ const allowlist = (process.env.PIEVO_ALLOWED_LOGINS || "").split(",").map((entry) => entry.trim().toLowerCase()).filter(Boolean);
38315
38298
  if (!allowlist.length) return true;
38316
38299
  const e = (email || "").toLowerCase();
38317
38300
  const at = e.indexOf("@");
@@ -38319,97 +38302,41 @@ function emailAllowed(email) {
38319
38302
  const domain = e.slice(at);
38320
38303
  return allowlist.some((entry) => entry === e || entry === domain || entry.startsWith("*@") && entry.slice(1) === domain);
38321
38304
  }
38322
- function teamNameForEmail(email) {
38323
- const local = (email || "").split("@")[0]?.trim();
38324
- return local ? `${local}'s Team` : "Personal Team";
38305
+ function rejectDisallowedEmail(email) {
38306
+ throw new APIError("FORBIDDEN", { message: `${(email || "").trim().toLowerCase() || "this account"} is not on the Pievo allowlist` });
38325
38307
  }
38326
- var TEAM_COOKIE = "pievo.team";
38327
- /**
38328
- * The signed-in user (id + email) for the current server-fn request, or null when
38329
- * no session. Reads the request via TanStack's async-local context, so it only
38330
- * works inside a server fn / server route handler.
38331
- */
38308
+ /** The Better Auth user for the current server request, or null without a session. */
38332
38309
  async function currentUser() {
38333
38310
  const { getRequest } = await import("./ssr.mjs").then((n) => n.i).then((n) => n.t);
38334
- const u = (await auth.api.getSession({ headers: getRequest().headers }))?.user;
38335
- return u ? {
38336
- id: u.id,
38337
- email: u.email ?? null
38311
+ const user = (await auth.api.getSession({ headers: getRequest().headers }))?.user;
38312
+ return user ? {
38313
+ id: user.id,
38314
+ email: user.email ?? null
38338
38315
  } : null;
38339
38316
  }
38340
38317
  async function currentUserId() {
38341
38318
  return (await currentUser())?.id ?? null;
38342
38319
  }
38343
- async function selectedTeam() {
38344
- const { getRequest } = await import("./ssr.mjs").then((n) => n.i).then((n) => n.t);
38345
- const raw = getRequest().headers.get("cookie") || "";
38346
- const v = new RegExp(`(?:^|;\\s*)${TEAM_COOKIE}=([^;]+)`).exec(raw)?.[1];
38347
- return v ? decodeURIComponent(v) : null;
38348
- }
38349
38320
  /**
38350
- * Per-request data scope. Machines and loops are scoped by `teamId`.
38351
- * The active team is resolved from (in precedence order) an EXPLICIT team — the
38352
- * `/t/<teamId>` route param, so a tab/bookmark pins its own team independent of
38353
- * any cookie; otherwise the `pievo.team` cookie (only a last-used
38354
- * default hint). Either source is VALIDATED here against membership, never
38355
- * trusted blind, and falls back to the user's personal team.
38356
- *
38357
- * `explicitTeam` (when a non-empty string) wins over the cookie. An unauthorized
38358
- * value falls through to the personal team exactly like a stale cookie, so the
38359
- * caller can detect rejection by comparing the returned `teamId` to what it asked
38360
- * for (see `canViewTeam` in loopApi) without this ever leaking another team's data.
38361
- */
38362
- async function requestScope(explicitTeam) {
38363
- const enforce = authEnabled;
38364
- if (!enforce) {
38365
- const teamId = teamIdForUser(null);
38366
- await ensureTeam(teamId, "Shared Workspace", null);
38367
- return {
38368
- enforce,
38369
- userId: null,
38370
- teamId
38371
- };
38372
- }
38373
- const user = await currentUser();
38374
- const userId = user?.id ?? null;
38375
- const personalTeam = teamIdForUser(userId);
38376
- await ensureTeam(personalTeam, userId ? teamNameForEmail(user?.email) : "Shared Workspace", userId);
38377
- const sel = explicitTeam != null && explicitTeam !== "" ? explicitTeam : await selectedTeam();
38378
- if (sel && sel !== personalTeam && userId) {
38379
- if (await isTeamMember(sel, userId)) return {
38380
- enforce,
38381
- userId,
38382
- teamId: sel
38383
- };
38384
- }
38321
+ * Auth mode uses the signed-in user as the sole tenant boundary. Open mode is
38322
+ * intentionally unscoped and does not perform a session lookup.
38323
+ */
38324
+ async function requestScope() {
38325
+ if (!authEnabled) return {
38326
+ enforce: false,
38327
+ userId: null
38328
+ };
38385
38329
  return {
38386
- enforce,
38387
- userId,
38388
- teamId: personalTeam
38330
+ enforce: true,
38331
+ userId: await currentUserId()
38389
38332
  };
38390
38333
  }
38391
38334
  /**
38392
- * Whether the request may view/act on a loop, by its owning team. The single
38393
- * source for loop authorization, shared by the server fns (`ownedLoop`) and the
38394
- * artifact download route so the gate can't drift between them.
38395
- *
38396
- * Authorization is by MEMBERSHIP in the loop's own team — NOT by the loop merely
38397
- * matching the caller's active team. A user who belongs to team B can open a
38398
- * direct link to a team-B loop while their active team is A, instead of getting a
38399
- * spurious "not found" (the cross-team-link bug). Rules:
38400
- * - open mode ⇒ the single shared workspace, everything visible;
38401
- * - the active team is a no-DB fast path (requestScope already membership-
38402
- * validated the active-team cookie);
38403
- * - otherwise the user must be a MEMBER of the loop's team.
38404
- * Async because the membership fall-through is a store lookup. A non-member (and a
38405
- * signed-out request) is denied, indistinguishable from a nonexistent loop.
38406
- */
38407
- async function canAccessLoop(loopTeamId, scope) {
38408
- const { enforce, teamId, userId } = scope;
38409
- if (!enforce) return true;
38410
- if (loopTeamId === teamId) return true;
38411
- if (!loopTeamId || !userId) return false;
38412
- return isTeamMember(loopTeamId, userId);
38335
+ * Shared loop authorization used by server functions and raw artifact serving.
38336
+ * A denied loop and an absent loop must remain externally indistinguishable.
38337
+ */
38338
+ function canAccessLoop(loopUserId, scope) {
38339
+ return !scope.enforce || !!scope.userId && loopUserId === scope.userId;
38413
38340
  }
38414
38341
  var auth = betterAuth({
38415
38342
  baseURL: process.env.PIEVO_BASE_URL || "http://127.0.0.1:3000",
@@ -38419,17 +38346,10 @@ var auth = betterAuth({
38419
38346
  clientId,
38420
38347
  clientSecret
38421
38348
  } } : {},
38422
- databaseHooks: { user: { create: {
38423
- before: async (user) => {
38424
- if (!emailAllowed(user.email)) throw new APIError("FORBIDDEN", { message: `${(user.email || "").toLowerCase()} is not on the Pievo allowlist` });
38425
- return { data: user };
38426
- },
38427
- after: async (user) => {
38428
- try {
38429
- await ensureTeam(teamIdForUser(user.id), teamNameForEmail(user.email), user.id);
38430
- } catch {}
38431
- }
38432
- } } }
38349
+ databaseHooks: { user: { create: { before: async (user) => {
38350
+ if (!emailAllowed(user.email)) rejectDisallowedEmail(user.email);
38351
+ return { data: user };
38352
+ } } } }
38433
38353
  });
38434
38354
  //#endregion
38435
- export { SqliteQueryCompiler as a, auth, authEnabled, canAccessLoop, currentUserId, SqliteAdapter as i, DefaultQueryCompiler as n, CompiledQuery as o, kyselyAdapter as r, requestScope, sql$1 as s, createKyselyAdapter as t };
38355
+ export { SqliteQueryCompiler as a, auth, authEnabled, canAccessLoop, SqliteAdapter as i, DefaultQueryCompiler as n, CompiledQuery as o, kyselyAdapter as r, requestScope, sql$1 as s, createKyselyAdapter as t };