@circuitwall/jarela 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  5. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  15. package/.next/standalone/.next/server/app/_not-found.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  22. package/.next/standalone/.next/server/app/api/v1/builtin-tools/route.js +10 -1
  23. package/.next/standalone/.next/server/app/api/v1/builtin-tools/route.js.map +1 -1
  24. package/.next/standalone/.next/server/app/api/v1/dashboard/currency/route.js +10 -5
  25. package/.next/standalone/.next/server/app/api/v1/dashboard/currency/route.js.map +1 -1
  26. package/.next/standalone/.next/server/app/api/v1/page-capture/route.js +37 -3
  27. package/.next/standalone/.next/server/app/api/v1/page-capture/route.js.map +1 -1
  28. package/.next/standalone/.next/server/app/api/v1/providers/[provider]/probe/route.js +9 -1
  29. package/.next/standalone/.next/server/app/api/v1/providers/[provider]/probe/route.js.map +1 -1
  30. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js +33 -8
  31. package/.next/standalone/.next/server/app/api/v1/threads/[thread_id]/run/route.js.map +1 -1
  32. package/.next/standalone/.next/server/app/page.js +73 -204
  33. package/.next/standalone/.next/server/app/page.js.map +1 -1
  34. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/setup/page.js +1 -1
  37. package/.next/standalone/.next/server/app/setup/page.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/chunks/1718.js +159 -0
  40. package/.next/standalone/.next/server/chunks/1718.js.map +1 -0
  41. package/.next/standalone/.next/server/chunks/2082.js +6 -3
  42. package/.next/standalone/.next/server/chunks/2082.js.map +1 -1
  43. package/.next/standalone/.next/server/chunks/210.js +28 -0
  44. package/.next/standalone/.next/server/chunks/210.js.map +1 -1
  45. package/.next/standalone/.next/server/chunks/423.js +6 -3
  46. package/.next/standalone/.next/server/chunks/423.js.map +1 -1
  47. package/.next/standalone/.next/server/chunks/4631.js +37 -5
  48. package/.next/standalone/.next/server/chunks/4631.js.map +1 -1
  49. package/.next/standalone/.next/server/chunks/8167.js +255 -204
  50. package/.next/standalone/.next/server/chunks/8167.js.map +1 -1
  51. package/.next/standalone/.next/server/chunks/8866.js +38 -5
  52. package/.next/standalone/.next/server/chunks/8866.js.map +1 -1
  53. package/.next/standalone/.next/server/chunks/9032.js +8 -0
  54. package/.next/standalone/.next/server/chunks/9032.js.map +1 -1
  55. package/.next/standalone/.next/server/chunks/{7883.js → 9557.js} +15 -3
  56. package/.next/standalone/.next/server/chunks/9557.js.map +1 -0
  57. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  58. package/.next/standalone/.next/server/middleware.js +6 -3
  59. package/.next/standalone/.next/server/pages/404.html +2 -2
  60. package/.next/standalone/.next/server/pages/500.html +1 -1
  61. package/.next/standalone/.next/server/proxy.js.map +1 -1
  62. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  63. package/.next/standalone/.next/static/chunks/{2351-68d8987bbe17ba2d.js → 2351-1ab119fb3b48f4c9.js} +258 -205
  64. package/.next/standalone/.next/static/chunks/2351-1ab119fb3b48f4c9.js.map +1 -0
  65. package/.next/standalone/.next/static/chunks/{9209-0d46118e502f8bf5.js → 4097-64691f9110cf167c.js} +14 -2
  66. package/.next/standalone/.next/static/chunks/4097-64691f9110cf167c.js.map +1 -0
  67. package/.next/standalone/.next/static/chunks/app/{page-2ab710949b62a638.js → page-145150e0468544e7.js} +74 -205
  68. package/.next/standalone/.next/static/chunks/app/page-145150e0468544e7.js.map +1 -0
  69. package/.next/standalone/.next/static/chunks/app/setup/{page-9a465b5fa755b3c3.js → page-a1463a9ace439ff7.js} +2 -2
  70. package/.next/standalone/.next/static/chunks/app/setup/{page-9a465b5fa755b3c3.js.map → page-a1463a9ace439ff7.js.map} +1 -1
  71. package/.next/standalone/.next/static/chunks/{webpack-ff5627013a5e3842.js → webpack-f4ac5c5f92cfd1c1.js} +13 -1
  72. package/.next/standalone/.next/static/chunks/webpack-f4ac5c5f92cfd1c1.js.map +1 -0
  73. package/.next/standalone/package.json +2 -1
  74. package/CHANGELOG.md +84 -0
  75. package/README.md +51 -26
  76. package/api/client.ts +10 -9
  77. package/app/api/v1/dashboard/currency/route.ts +7 -2
  78. package/app/api/v1/providers/[provider]/probe/route.ts +12 -1
  79. package/app/api/v1/threads/[thread_id]/run/route.ts +22 -8
  80. package/components/chat/InputBar.tsx +10 -1
  81. package/components/layout/AppShell.tsx +53 -17
  82. package/components/setup/PinKeypad.tsx +238 -0
  83. package/components/setup/ScreenLock.tsx +8 -173
  84. package/components/setup/UnlockScreen.tsx +25 -192
  85. package/lib/api/page-capture.test.ts +58 -0
  86. package/lib/api/page-capture.ts +31 -1
  87. package/lib/documents/remote/github.ts +16 -2
  88. package/lib/documents/remote/mail.ts +11 -2
  89. package/lib/lifecycle/shutdown.ts +9 -0
  90. package/lib/providers/github-copilot-auth.ts +2 -0
  91. package/lib/providers/github-copilot.ts +1 -0
  92. package/lib/tools/async-results.ts +11 -0
  93. package/package.json +2 -1
  94. package/scripts/install-to-system.ps1 +2 -2
  95. package/scripts/installed-launcher.ps1 +81 -17
  96. package/.next/standalone/.next/server/chunks/7883.js.map +0 -1
  97. package/.next/standalone/.next/static/chunks/2351-68d8987bbe17ba2d.js.map +0 -1
  98. package/.next/standalone/.next/static/chunks/9209-0d46118e502f8bf5.js.map +0 -1
  99. package/.next/standalone/.next/static/chunks/app/page-2ab710949b62a638.js.map +0 -1
  100. package/.next/standalone/.next/static/chunks/webpack-ff5627013a5e3842.js.map +0 -1
  101. /package/.next/standalone/.next/static/{ZKy7LJ3KXj2TIyKOg_fBH → WQdcnm9NyqpeNc0Z8_woo}/_buildManifest.js +0 -0
  102. /package/.next/standalone/.next/static/{ZKy7LJ3KXj2TIyKOg_fBH → WQdcnm9NyqpeNc0Z8_woo}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- {"node":{},"edge":{},"encryptionKey":"vJ4lqb4BX4SZF7/2F4JAgAZEiR0pPgg0UgfHJqxfoOw="}
1
+ {"node":{},"edge":{},"encryptionKey":"3ec6+G/tKi1SBOGgPaFEga0lY1bQVWG8P8Wez8h2IBE="}
@@ -292,6 +292,239 @@ function MarkdownTextarea({ value, onChange, className = "", placeholder, rows,
292
292
  }
293
293
 
294
294
 
295
+ /***/ }),
296
+
297
+ /***/ 772:
298
+ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
299
+
300
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
301
+ /* harmony export */ y: () => (/* binding */ PinKeypad)
302
+ /* harmony export */ });
303
+ /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5155);
304
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2115);
305
+ /* harmony import */ var _components_ui_Logo__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8439);
306
+ /* __next_internal_client_entry_do_not_use__ PinKeypad auto */
307
+
308
+
309
+ // Shared 6-digit PIN keypad used by both the decrypt splash (master key
310
+ // locked at boot) and the screen-lock overlay (idle timer fired). The
311
+ // only differences between the two states are (a) the endpoint hit
312
+ // (b) the copy on screen and (c) what happens after success. Everything
313
+ // else — keypad layout, dot strip, rate-limit handling, keyboard
314
+ // support, error mapping — is identical, so the two were collapsed into
315
+ // one component to avoid drift.
316
+ const PIN_LENGTH = 6;
317
+ const MODES = {
318
+ decrypt: {
319
+ endpoint: "/api/v1/security/unlock",
320
+ title: "Decrypt Jarela",
321
+ subtitle: "Enter your 6-digit PIN to decrypt your data.",
322
+ busyLabel: "Decrypting\u2026"
323
+ },
324
+ unlock: {
325
+ endpoint: "/api/v1/security/verify-pin",
326
+ title: "Welcome back",
327
+ subtitle: "Enter your 6-digit PIN to unlock.",
328
+ busyLabel: "Unlocking\u2026"
329
+ }
330
+ };
331
+ function PinKeypad({ mode, onSuccess }) {
332
+ const cfg = MODES[mode];
333
+ const [digits, setDigits] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("");
334
+ const [error, setError] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(null);
335
+ const [submitting, setSubmitting] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
336
+ const [retryAfterSec, setRetryAfterSec] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(0);
337
+ // Hard guard against parallel submits. setState updaters can run more
338
+ // than once (dev StrictMode, concurrent rendering), so if `submit`
339
+ // were called from inside `setDigits` we'd POST twice and the second
340
+ // request would race the first into `unlockMasterKey()` after state
341
+ // already flipped to unlocked — the route would 500.
342
+ const submittingRef = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(false);
343
+ const submit = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)(async (pin)=>{
344
+ if (submittingRef.current) return;
345
+ submittingRef.current = true;
346
+ setSubmitting(true);
347
+ setError(null);
348
+ try {
349
+ const res = await fetch(cfg.endpoint, {
350
+ method: "POST",
351
+ headers: {
352
+ "content-type": "application/json"
353
+ },
354
+ body: JSON.stringify({
355
+ pin
356
+ })
357
+ });
358
+ if (res.ok) {
359
+ onSuccess();
360
+ return;
361
+ }
362
+ const body = await res.json().catch(()=>({}));
363
+ // Goal-state convergence: both endpoints can return 409 when the
364
+ // server already reached the target state (decrypt → master key
365
+ // already unlocked; unlock → screen not locked). Treat as success
366
+ // rather than surfacing a confusing error.
367
+ if (res.status === 409 && (body.error === "not-locked" || body.error === "no-pin")) {
368
+ onSuccess();
369
+ return;
370
+ }
371
+ if (res.status === 429 && typeof body.retry_after_ms === "number") {
372
+ setRetryAfterSec(Math.ceil(body.retry_after_ms / 1000));
373
+ setError("Too many attempts. Try again later.");
374
+ } else if (res.status === 401) {
375
+ setError("Wrong PIN. Try again.");
376
+ } else if (res.status === 400) {
377
+ setError("Invalid PIN format.");
378
+ } else {
379
+ setError(body.error ?? `Error (${res.status})`);
380
+ }
381
+ setDigits("");
382
+ } catch (err) {
383
+ setError(err instanceof Error ? err.message : String(err));
384
+ setDigits("");
385
+ } finally{
386
+ submittingRef.current = false;
387
+ setSubmitting(false);
388
+ }
389
+ }, [
390
+ cfg.endpoint,
391
+ onSuccess
392
+ ]);
393
+ const append = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)((d)=>{
394
+ if (submitting || retryAfterSec > 0) return;
395
+ setError(null);
396
+ setDigits((cur)=>cur.length >= PIN_LENGTH ? cur : cur + d);
397
+ }, [
398
+ submitting,
399
+ retryAfterSec
400
+ ]);
401
+ // Auto-submit once the buffer hits 6 digits.
402
+ (0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
403
+ if (digits.length === PIN_LENGTH && !submittingRef.current) {
404
+ void submit(digits);
405
+ }
406
+ }, [
407
+ digits,
408
+ submit
409
+ ]);
410
+ const backspace = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)(()=>{
411
+ if (submitting) return;
412
+ setError(null);
413
+ setDigits((cur)=>cur.slice(0, -1));
414
+ }, [
415
+ submitting
416
+ ]);
417
+ (0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
418
+ function onKey(e) {
419
+ if (/^[0-9]$/.test(e.key)) {
420
+ e.preventDefault();
421
+ append(e.key);
422
+ } else if (e.key === "Backspace") {
423
+ e.preventDefault();
424
+ backspace();
425
+ }
426
+ }
427
+ window.addEventListener("keydown", onKey);
428
+ return ()=>window.removeEventListener("keydown", onKey);
429
+ }, [
430
+ append,
431
+ backspace
432
+ ]);
433
+ (0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
434
+ if (retryAfterSec <= 0) return;
435
+ const t = setInterval(()=>{
436
+ setRetryAfterSec((s)=>s > 0 ? s - 1 : 0);
437
+ }, 1000);
438
+ return ()=>clearInterval(t);
439
+ }, [
440
+ retryAfterSec
441
+ ]);
442
+ return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
443
+ className: "fixed inset-0 z-[1000] flex flex-col items-center justify-center gap-6 bg-surface text-fg animate-in fade-in duration-200",
444
+ style: {
445
+ paddingTop: "env(safe-area-inset-top)",
446
+ paddingBottom: "env(safe-area-inset-bottom)"
447
+ },
448
+ "data-pin-mode": mode,
449
+ children: [
450
+ /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_ui_Logo__WEBPACK_IMPORTED_MODULE_2__/* .Logo */ .g, {
451
+ className: "h-16 w-auto"
452
+ }),
453
+ /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
454
+ className: "w-full max-w-xs p-6",
455
+ children: [
456
+ /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h1", {
457
+ className: "mb-1 text-center text-lg font-semibold text-fg",
458
+ children: cfg.title
459
+ }),
460
+ /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
461
+ className: "mb-6 text-center text-xs text-fg-faint",
462
+ children: cfg.subtitle
463
+ }),
464
+ /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
465
+ className: "mb-6 flex justify-center gap-3",
466
+ "aria-label": "PIN entry progress",
467
+ children: Array.from({
468
+ length: PIN_LENGTH
469
+ }).map((_, i)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
470
+ className: `h-3 w-3 rounded-full transition-colors ${i < digits.length ? error ? "bg-red-500" : "bg-fg" : "bg-surface-3"}`
471
+ }, i))
472
+ }),
473
+ /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
474
+ className: "grid grid-cols-3 gap-2",
475
+ children: [
476
+ [
477
+ "1",
478
+ "2",
479
+ "3",
480
+ "4",
481
+ "5",
482
+ "6",
483
+ "7",
484
+ "8",
485
+ "9"
486
+ ].map((d)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(PinKey, {
487
+ digit: d,
488
+ onPress: ()=>append(d),
489
+ disabled: submitting || retryAfterSec > 0
490
+ }, d)),
491
+ /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {}),
492
+ /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(PinKey, {
493
+ digit: "0",
494
+ onPress: ()=>append("0"),
495
+ disabled: submitting || retryAfterSec > 0
496
+ }),
497
+ /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(PinKey, {
498
+ digit: "\\u2190",
499
+ onPress: backspace,
500
+ disabled: submitting || digits.length === 0,
501
+ ariaLabel: "Backspace"
502
+ })
503
+ ]
504
+ }),
505
+ /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
506
+ className: `mt-4 min-h-[1.5rem] text-center text-xs ${error ? "text-red-400" : "text-fg-faint"}`,
507
+ role: "status",
508
+ "aria-live": "polite",
509
+ children: retryAfterSec > 0 ? `Try again in ${retryAfterSec}s` : error ?? (submitting ? cfg.busyLabel : "\u00A0")
510
+ })
511
+ ]
512
+ })
513
+ ]
514
+ });
515
+ }
516
+ function PinKey({ digit, onPress, disabled, ariaLabel }) {
517
+ return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
518
+ type: "button",
519
+ onClick: onPress,
520
+ disabled: disabled,
521
+ "aria-label": ariaLabel ?? digit,
522
+ className: "h-14 rounded-xl bg-surface-3 text-xl font-medium text-fg transition-colors hover:bg-surface-3/70 active:bg-surface-3/50 disabled:cursor-not-allowed disabled:opacity-50",
523
+ children: digit
524
+ });
525
+ }
526
+
527
+
295
528
  /***/ }),
296
529
 
297
530
  /***/ 1490:
@@ -395,15 +628,19 @@ async function request(path, init) {
395
628
  signal: timeoutCtrl.signal
396
629
  });
397
630
  if (!res.ok) {
398
- // 423 screen-locked: surface a window event so the AppShell can
399
- // mount the ScreenLock overlay. We still throw so the caller
400
- // sees the failure, but no point retrying the lock isn't
401
- // going to clear on its own.
631
+ // 423 lock states: distinct events so AppShell can mount the
632
+ // right overlay (decrypt vs presence-check). Both throw so the
633
+ // caller still sees the failure no point retrying, the lock
634
+ // isn't going to clear on its own.
402
635
  if (res.status === 423) {
403
636
  const cloned = res.clone();
404
637
  const body = await cloned.json().catch(()=>null);
405
- if (body?.error === "screen-locked" && "object" !== "undefined") {
406
- window.dispatchEvent(new CustomEvent("jarela:screen-locked"));
638
+ if (true) {
639
+ if (body?.error === "screen-locked") {
640
+ window.dispatchEvent(new CustomEvent("jarela:screen-locked"));
641
+ } else if (body?.error === "locked") {
642
+ window.dispatchEvent(new CustomEvent("jarela:master-key-locked"));
643
+ }
407
644
  }
408
645
  throw new Error(`423 ${body?.error ?? "locked"}`);
409
646
  }
@@ -1270,209 +1507,25 @@ function CapBadges({ caps, provider, modelId, size = "xs" }) {
1270
1507
  /* harmony export */ UnlockScreen: () => (/* binding */ UnlockScreen)
1271
1508
  /* harmony export */ });
1272
1509
  /* harmony import */ var react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5155);
1273
- /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2115);
1274
- /* harmony import */ var _components_ui_Logo__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8439);
1510
+ /* harmony import */ var next_navigation__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3321);
1511
+ /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(2115);
1512
+ /* harmony import */ var _PinKeypad__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(772);
1275
1513
  /* __next_internal_client_entry_do_not_use__ UnlockScreen auto */
1276
1514
 
1277
1515
 
1278
- // PIN unlock splash for ADR-0063 PIN-wrapped keyfiles. Rendered by the
1279
- // root route when the server detects the master key is locked. Submits
1280
- // the 6-digit PIN to /api/v1/security/unlock; on success, reloads so
1281
- // the server re-renders into the normal authenticated tree.
1282
- const PIN_LENGTH = 6;
1283
- function UnlockScreen() {
1284
- const [digits, setDigits] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)("");
1285
- const [error, setError] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(null);
1286
- const [submitting, setSubmitting] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
1287
- const [retryAfterSec, setRetryAfterSec] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(0);
1288
- const containerRef = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
1289
- // Hard guard against parallel submits. setState updaters can run more
1290
- // than once (dev StrictMode, concurrent rendering), so if `submit`
1291
- // were called from inside `setDigits` we'd POST twice and the second
1292
- // request would race the first into `unlockMasterKey()` after state
1293
- // already flipped to unlocked - the route would 500.
1294
- const submittingRef = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(false);
1295
- const submit = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)(async (pin)=>{
1296
- if (submittingRef.current) return;
1297
- submittingRef.current = true;
1298
- setSubmitting(true);
1299
- setError(null);
1300
- try {
1301
- const res = await fetch("/api/v1/security/unlock", {
1302
- method: "POST",
1303
- headers: {
1304
- "content-type": "application/json"
1305
- },
1306
- body: JSON.stringify({
1307
- pin
1308
- })
1309
- });
1310
- if (res.ok) {
1311
- window.location.reload();
1312
- return;
1313
- }
1314
- const body = await res.json().catch(()=>({}));
1315
- if (res.status === 409 && body.error === "not-locked") {
1316
- // Master key was already unlocked (host typed the PIN, or
1317
- // another tab beat us to it). The goal state is reached —
1318
- // reload into the app shell.
1319
- window.location.reload();
1320
- return;
1321
- }
1322
- if (res.status === 429 && typeof body.retry_after_ms === "number") {
1323
- setRetryAfterSec(Math.ceil(body.retry_after_ms / 1000));
1324
- setError("Too many attempts. Try again later.");
1325
- } else if (res.status === 401) {
1326
- setError("Wrong PIN. Try again.");
1327
- } else if (res.status === 400) {
1328
- setError("Invalid PIN format.");
1329
- } else {
1330
- setError(body.error ?? `Error (${res.status})`);
1331
- }
1332
- setDigits("");
1333
- } catch (err) {
1334
- setError(err instanceof Error ? err.message : String(err));
1335
- setDigits("");
1336
- } finally{
1337
- submittingRef.current = false;
1338
- setSubmitting(false);
1339
- }
1340
- }, []);
1341
- const append = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)((d)=>{
1342
- if (submitting || retryAfterSec > 0) return;
1343
- setError(null);
1344
- setDigits((cur)=>cur.length >= PIN_LENGTH ? cur : cur + d);
1345
- }, [
1346
- submitting,
1347
- retryAfterSec
1348
- ]);
1349
- // Auto-submit once the buffer hits 6 digits. Effect runs once per
1350
- // state transition (not per updater invocation), so we POST exactly
1351
- // one time even under StrictMode double-render.
1352
- (0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
1353
- if (digits.length === PIN_LENGTH && !submittingRef.current) {
1354
- void submit(digits);
1355
- }
1356
- }, [
1357
- digits,
1358
- submit
1359
- ]);
1360
- const backspace = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)(()=>{
1361
- if (submitting) return;
1362
- setError(null);
1363
- setDigits((cur)=>cur.slice(0, -1));
1364
- }, [
1365
- submitting
1366
- ]);
1367
- // Physical keyboard support.
1368
- (0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
1369
- function onKey(e) {
1370
- if (/^[0-9]$/.test(e.key)) {
1371
- e.preventDefault();
1372
- append(e.key);
1373
- } else if (e.key === "Backspace") {
1374
- e.preventDefault();
1375
- backspace();
1376
- }
1377
- }
1378
- window.addEventListener("keydown", onKey);
1379
- return ()=>window.removeEventListener("keydown", onKey);
1380
- }, [
1381
- append,
1382
- backspace
1383
- ]);
1384
- // Tick down the rate-limit countdown.
1385
- (0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(()=>{
1386
- if (retryAfterSec <= 0) return;
1387
- const t = setInterval(()=>{
1388
- setRetryAfterSec((s)=>s > 0 ? s - 1 : 0);
1389
- }, 1000);
1390
- return ()=>clearInterval(t);
1516
+
1517
+ function UnlockScreen({ onUnlock } = {}) {
1518
+ const router = (0,next_navigation__WEBPACK_IMPORTED_MODULE_1__.useRouter)();
1519
+ const onSuccess = (0,react__WEBPACK_IMPORTED_MODULE_2__.useCallback)(()=>{
1520
+ if (onUnlock) onUnlock();
1521
+ else router.refresh();
1391
1522
  }, [
1392
- retryAfterSec
1523
+ onUnlock,
1524
+ router
1393
1525
  ]);
1394
- return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
1395
- ref: containerRef,
1396
- className: "fixed inset-0 z-[1000] flex flex-col items-center justify-center gap-6 bg-surface text-fg",
1397
- style: {
1398
- paddingTop: "env(safe-area-inset-top)",
1399
- paddingBottom: "env(safe-area-inset-bottom)"
1400
- },
1401
- children: [
1402
- /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_components_ui_Logo__WEBPACK_IMPORTED_MODULE_2__/* .Logo */ .g, {
1403
- className: "h-16 w-auto"
1404
- }),
1405
- /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
1406
- className: "w-full max-w-xs p-6",
1407
- children: [
1408
- /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("h1", {
1409
- className: "mb-1 text-center text-lg font-semibold text-fg",
1410
- children: "Unlock Jarela"
1411
- }),
1412
- /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
1413
- className: "mb-6 text-center text-xs text-fg-faint",
1414
- children: "Enter your 6-digit PIN to decrypt your data."
1415
- }),
1416
- /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
1417
- className: "mb-6 flex justify-center gap-3",
1418
- "aria-label": "PIN entry progress",
1419
- children: Array.from({
1420
- length: PIN_LENGTH
1421
- }).map((_, i)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
1422
- className: `h-3 w-3 rounded-full transition-colors ${i < digits.length ? error ? "bg-red-500" : "bg-fg" : "bg-surface-3"}`
1423
- }, i))
1424
- }),
1425
- /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
1426
- className: "grid grid-cols-3 gap-2",
1427
- children: [
1428
- [
1429
- "1",
1430
- "2",
1431
- "3",
1432
- "4",
1433
- "5",
1434
- "6",
1435
- "7",
1436
- "8",
1437
- "9"
1438
- ].map((d)=>/*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(PinKey, {
1439
- digit: d,
1440
- onPress: ()=>append(d),
1441
- disabled: submitting || retryAfterSec > 0
1442
- }, d)),
1443
- /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {}),
1444
- /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(PinKey, {
1445
- digit: "0",
1446
- onPress: ()=>append("0"),
1447
- disabled: submitting || retryAfterSec > 0
1448
- }),
1449
- /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(PinKey, {
1450
- digit: "←",
1451
- onPress: backspace,
1452
- disabled: submitting || digits.length === 0,
1453
- ariaLabel: "Backspace"
1454
- })
1455
- ]
1456
- }),
1457
- /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("p", {
1458
- className: `mt-4 min-h-[1.5rem] text-center text-xs ${error ? "text-red-400" : "text-fg-faint"}`,
1459
- role: "status",
1460
- "aria-live": "polite",
1461
- children: retryAfterSec > 0 ? `Try again in ${retryAfterSec}s` : error ?? (submitting ? "Unlocking…" : "\u00A0")
1462
- })
1463
- ]
1464
- })
1465
- ]
1466
- });
1467
- }
1468
- function PinKey({ digit, onPress, disabled, ariaLabel }) {
1469
- return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
1470
- type: "button",
1471
- onClick: onPress,
1472
- disabled: disabled,
1473
- "aria-label": ariaLabel ?? digit,
1474
- className: "h-14 rounded-xl bg-surface-3 text-xl font-medium text-fg transition-colors hover:bg-surface-3/70 active:bg-surface-3/50 disabled:cursor-not-allowed disabled:opacity-50",
1475
- children: digit
1526
+ return /*#__PURE__*/ (0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(_PinKeypad__WEBPACK_IMPORTED_MODULE_3__/* .PinKeypad */ .y, {
1527
+ mode: "decrypt",
1528
+ onSuccess: onSuccess
1476
1529
  });
1477
1530
  }
1478
1531
 
@@ -2865,4 +2918,4 @@ function computeFeatureReadiness({ models, integrations, selectedProvider, selec
2865
2918
  /***/ })
2866
2919
 
2867
2920
  }]);
2868
- //# sourceMappingURL=2351-68d8987bbe17ba2d.js.map
2921
+ //# sourceMappingURL=2351-1ab119fb3b48f4c9.js.map