@hachej/boring-core 0.1.59 → 0.1.60

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.
@@ -17,6 +17,109 @@
17
17
  );
18
18
  }
19
19
 
20
+ /* --- Public top-bar CTA + Calendly popover --- */
21
+ .public-topbar-actions {
22
+ display: inline-flex;
23
+ align-items: center;
24
+ gap: 8px;
25
+ }
26
+
27
+ .public-contact-popover-wrap {
28
+ position: relative;
29
+ }
30
+
31
+ .public-contact-button,
32
+ .public-sign-in-button {
33
+ border: 1px solid var(--border);
34
+ border-radius: 8px;
35
+ padding: 6px 12px;
36
+ font-size: 13px;
37
+ font-weight: 600;
38
+ line-height: 1.2;
39
+ transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
40
+ }
41
+
42
+ .public-contact-button {
43
+ background: var(--foreground);
44
+ color: var(--background);
45
+ border-color: var(--foreground);
46
+ }
47
+
48
+ .public-contact-button:hover {
49
+ background: color-mix(in oklch, var(--foreground) 88%, var(--accent));
50
+ }
51
+
52
+ .public-sign-in-button {
53
+ background: transparent;
54
+ color: var(--foreground);
55
+ }
56
+
57
+ .public-sign-in-button:hover {
58
+ background: var(--muted);
59
+ }
60
+
61
+ .public-contact-popover {
62
+ position: absolute;
63
+ top: calc(100% + 10px);
64
+ right: 0;
65
+ z-index: 80;
66
+ width: min(420px, calc(100vw - 24px));
67
+ overflow: hidden;
68
+ border: 1px solid var(--border);
69
+ border-radius: 18px;
70
+ background: var(--background);
71
+ box-shadow: 0 24px 80px color-mix(in oklch, var(--foreground) 18%, transparent);
72
+ }
73
+
74
+ .public-contact-popover-header {
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: space-between;
78
+ gap: 12px;
79
+ border-bottom: 1px solid var(--border);
80
+ padding: 10px 12px 10px 14px;
81
+ font-size: 13px;
82
+ font-weight: 700;
83
+ }
84
+
85
+ .public-contact-popover-header button {
86
+ display: grid;
87
+ width: 28px;
88
+ height: 28px;
89
+ place-items: center;
90
+ border: 0;
91
+ border-radius: 8px;
92
+ background: transparent;
93
+ color: var(--muted-foreground);
94
+ cursor: pointer;
95
+ font-size: 20px;
96
+ line-height: 1;
97
+ }
98
+
99
+ .public-contact-popover-header button:hover {
100
+ background: var(--muted);
101
+ color: var(--foreground);
102
+ }
103
+
104
+ .public-contact-popover iframe {
105
+ display: block;
106
+ width: 100%;
107
+ height: min(680px, calc(100vh - 120px));
108
+ min-height: 520px;
109
+ border: 0;
110
+ background: var(--background);
111
+ }
112
+
113
+ @media (max-width: 560px) {
114
+ .public-contact-popover {
115
+ position: fixed;
116
+ top: 56px;
117
+ right: 8px;
118
+ left: 8px;
119
+ width: auto;
120
+ }
121
+ }
122
+
20
123
  /* --- Hand-drawn teaching annotations (public no-auth shell) --- */
21
124
  .public-arrow {
22
125
  pointer-events: none;
@@ -165,7 +268,7 @@
165
268
  padding-top: clamp(28px, 9vh, 132px);
166
269
  }
167
270
 
168
- /* Move the /command cards to the bottom of the hero block (after the footer),
271
+ /* Move suggestion cards to the bottom of the hero block (after the footer),
169
272
  so they sit directly above the composer. The empty-state is a flex column,
170
273
  so `order` reorders without touching the React tree. */
171
274
  .public-chat-first-shell [data-boring-agent-part="empty-state"] .public-hero-foot {
@@ -175,7 +278,7 @@
175
278
  order: 20;
176
279
  }
177
280
 
178
- /* Command launcher cards — make the slash commands read as runnable. */
281
+ /* Suggestion cards — keep example prompts visually connected to the composer. */
179
282
  .public-chat-first-shell [data-boring-agent-part="suggestion-grid"] {
180
283
  margin-top: 28px;
181
284
  max-width: 560px;
@@ -26,6 +26,16 @@ interface ChatFirstPublicShellOptions {
26
26
  label: string;
27
27
  available?: boolean;
28
28
  }>;
29
+ /**
30
+ * Optional public top-bar contact CTA. When set, the no-auth top bar shows a
31
+ * “Get in touch” button that opens a Calendly popover instead of asking users
32
+ * to discover hidden chat commands.
33
+ */
34
+ contact?: {
35
+ label?: string;
36
+ calendlyUrl: string;
37
+ title?: string;
38
+ };
29
39
  /**
30
40
  * Hand-drawn "Ask your AI here" / "Review its work here" annotations point at the
31
41
  * composer and the workspace surface to teach the empty public shell. Apps
@@ -9,7 +9,7 @@ import {
9
9
  useSignIn,
10
10
  useSignUp,
11
11
  useWorkspaceRouteStatus
12
- } from "../../chunk-Q53N4QUK.js";
12
+ } from "../../chunk-NC46VGXS.js";
13
13
  import "../../chunk-HYNKZSTF.js";
14
14
  import {
15
15
  isRuntimeEmailVerificationEnabled
@@ -18,7 +18,7 @@ import "../../chunk-QZGYKLXB.js";
18
18
  import "../../chunk-MLKGABMK.js";
19
19
 
20
20
  // src/app/front/CoreWorkspaceAgentFront.tsx
21
- import { useEffect as useEffect3, useMemo, useState as useState3 } from "react";
21
+ import { useEffect as useEffect2, useMemo, useState as useState3 } from "react";
22
22
  import { Navigate, Route, useLocation as useLocation2, useParams } from "react-router-dom";
23
23
  import { WorkspaceProvider } from "@hachej/boring-workspace";
24
24
  import { ErrorState } from "@hachej/boring-ui-kit";
@@ -103,10 +103,9 @@ function ChatFirstAuthenticatedShell({
103
103
  }
104
104
 
105
105
  // src/app/front/chatFirst/ChatFirstPublicShell.tsx
106
- import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
106
+ import { useState as useState2 } from "react";
107
107
  import { useLocation } from "react-router-dom";
108
108
  import { builtinCommands } from "@hachej/boring-agent/front";
109
- import { postUiCommand } from "@hachej/boring-workspace";
110
109
 
111
110
  // src/app/front/chatFirst/AuthCard.tsx
112
111
  import { useState } from "react";
@@ -265,6 +264,25 @@ function readComposerDraftFromDom() {
265
264
  const input = document.querySelector('[data-boring-agent-part="composer-input"]');
266
265
  return input?.value ?? "";
267
266
  }
267
+ function PublicTopBarActions({
268
+ contact,
269
+ onSignIn
270
+ }) {
271
+ const [contactOpen, setContactOpen] = useState2(false);
272
+ return /* @__PURE__ */ jsxs3("div", { className: "public-topbar-actions", children: [
273
+ contact ? /* @__PURE__ */ jsxs3("div", { className: "public-contact-popover-wrap", children: [
274
+ /* @__PURE__ */ jsx4("button", { type: "button", className: "public-contact-button", onClick: () => setContactOpen((open) => !open), children: contact.label ?? "Get in touch" }),
275
+ contactOpen ? /* @__PURE__ */ jsxs3("div", { className: "public-contact-popover", role: "dialog", "aria-label": contact.title ?? "Schedule a call", children: [
276
+ /* @__PURE__ */ jsxs3("div", { className: "public-contact-popover-header", children: [
277
+ /* @__PURE__ */ jsx4("span", { children: contact.title ?? "Schedule a call" }),
278
+ /* @__PURE__ */ jsx4("button", { type: "button", "aria-label": "Close contact popover", onClick: () => setContactOpen(false), children: "\xD7" })
279
+ ] }),
280
+ /* @__PURE__ */ jsx4("iframe", { title: contact.title ?? "Schedule a call", src: contact.calendlyUrl, loading: "lazy" })
281
+ ] }) : null
282
+ ] }) : null,
283
+ /* @__PURE__ */ jsx4("button", { type: "button", className: "public-sign-in-button", onClick: onSignIn, children: "Sign in" })
284
+ ] });
285
+ }
268
286
  function ChatFirstPublicShell({
269
287
  appTitle,
270
288
  intendedWorkspaceId,
@@ -273,7 +291,6 @@ function ChatFirstPublicShell({
273
291
  }) {
274
292
  const location = useLocation();
275
293
  const [modalOpen, setModalOpen] = useState2(false);
276
- const lastAutoRunCommandRef = useRef("");
277
294
  const returnTo = safeReturnTo(location.pathname, location.search, location.hash);
278
295
  const promptedDraft = new URLSearchParams(location.search).get("prompt")?.trim() ?? "";
279
296
  const pendingReturnTo = promptedDraft ? "/" : returnTo;
@@ -284,91 +301,6 @@ function ChatFirstPublicShell({
284
301
  writePendingChatEntry({ draft, returnTo: pendingReturnTo, ...intendedWorkspaceId ? { intendedWorkspaceId } : {} });
285
302
  setModalOpen(true);
286
303
  };
287
- const normalizePublicCommand = (draft) => draft.trim().toLowerCase().replace(/[’`]/g, "'");
288
- const openLandingPage = () => postUiCommand({
289
- kind: "openPanel",
290
- params: { id: "public-landing-page", component: "public.launch.landing", title: "Landing page" }
291
- });
292
- const openLetsChat = () => postUiCommand({
293
- kind: "openPanel",
294
- params: { id: "public-lets-chat", component: "public.launch.lets-chat", title: "Let\u2019s chat" }
295
- });
296
- const publicCommands = [
297
- {
298
- name: "landing-page",
299
- description: "Open the landing page workspace tab.",
300
- kind: "local",
301
- handler: () => {
302
- openLandingPage();
303
- return { message: "Opened Landing page." };
304
- }
305
- },
306
- {
307
- name: "reach-out",
308
- description: "Open the Calendly workspace tab.",
309
- kind: "local",
310
- handler: () => {
311
- openLetsChat();
312
- return { message: "Opened Let\u2019s chat." };
313
- }
314
- }
315
- ];
316
- const runPublicCommand = (draft) => {
317
- const command = normalizePublicCommand(draft);
318
- if (command === "/landing-page") {
319
- openLandingPage();
320
- return true;
321
- }
322
- if (command === "/reach-out" || command === "/let's-chat" || command === "/lets-chat") {
323
- openLetsChat();
324
- return true;
325
- }
326
- return false;
327
- };
328
- useEffect2(() => {
329
- const intercept = (event) => {
330
- const draft = readComposerDraftFromDom();
331
- if (!runPublicCommand(draft)) return false;
332
- event.preventDefault();
333
- event.stopPropagation();
334
- return true;
335
- };
336
- const onClick = (event) => {
337
- const target = event.target instanceof Element ? event.target : null;
338
- if (!target?.closest('[data-boring-agent-part="composer-submit"], [aria-label="Submit"]')) return;
339
- intercept(event);
340
- };
341
- const maybeAutoRunDraft = () => {
342
- const draft = readComposerDraftFromDom();
343
- const command = normalizePublicCommand(draft);
344
- const isCommand = command === "/landing-page" || command === "/reach-out" || command === "/let's-chat" || command === "/lets-chat";
345
- if (!isCommand) {
346
- lastAutoRunCommandRef.current = "";
347
- return;
348
- }
349
- if (lastAutoRunCommandRef.current === command) return;
350
- lastAutoRunCommandRef.current = command;
351
- runPublicCommand(draft);
352
- };
353
- const onKeyDown = (event) => {
354
- const target = event.target instanceof Element ? event.target : null;
355
- if (!target?.closest('[data-boring-agent-part="composer-input"]')) return;
356
- if (event.key === "Enter" && !event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey) intercept(event);
357
- };
358
- const onInput = (event) => {
359
- const target = event.target instanceof Element ? event.target : null;
360
- if (!target?.closest('[data-boring-agent-part="composer-input"]')) return;
361
- window.setTimeout(maybeAutoRunDraft, 0);
362
- };
363
- document.addEventListener("click", onClick, true);
364
- document.addEventListener("keydown", onKeyDown, true);
365
- document.addEventListener("input", onInput, true);
366
- return () => {
367
- document.removeEventListener("click", onClick, true);
368
- document.removeEventListener("keydown", onKeyDown, true);
369
- document.removeEventListener("input", onInput, true);
370
- };
371
- });
372
304
  return /* @__PURE__ */ jsxs3("div", { className: "public-chat-first-shell relative h-screen min-h-0 bg-background", children: [
373
305
  publicShell?.showTeachingArrows !== false && /* @__PURE__ */ jsxs3(Fragment2, { children: [
374
306
  /* @__PURE__ */ jsxs3("div", { className: "public-arrow public-arrow-computer", "aria-hidden": "true", children: [
@@ -411,9 +343,9 @@ function ChatFirstPublicShell({
411
343
  ...workspaceProps,
412
344
  // No-auth landing should open with the workspace surface closed —
413
345
  // a fresh load/hard refresh shows just the hero, not a re-opened
414
- // panel. The /landing-page command still opens it on demand.
415
- defaultSurfaceOpen: false,
416
- topBarRight: /* @__PURE__ */ jsx4("button", { type: "button", className: "rounded-md border border-border px-3 py-1.5 text-sm font-medium hover:bg-muted", onClick: () => openAuth(), children: "Sign in" }),
346
+ // panel unless the app explicitly overrides defaultSurfaceOpen.
347
+ defaultSurfaceOpen: workspaceProps.defaultSurfaceOpen ?? false,
348
+ topBarRight: /* @__PURE__ */ jsx4(PublicTopBarActions, { contact: publicShell?.contact, onSignIn: () => openAuth() }),
417
349
  // No-auth shell has no real session yet — show just the brand, hide the
418
350
  // "· New session" placeholder that the default TopBar would render.
419
351
  topBarLeft: /* @__PURE__ */ jsxs3(Fragment2, { children: [
@@ -433,7 +365,7 @@ function ChatFirstPublicShell({
433
365
  chatParams: {
434
366
  ...workspaceProps.chatParams,
435
367
  emptyPlacement: "hero",
436
- composerPlaceholder: publicShell?.composerPlaceholder ?? "Type /landing-page or /reach-out",
368
+ composerPlaceholder: publicShell?.composerPlaceholder ?? "Sign in to continue in a private workspace",
437
369
  hideComposerSettings: !showComposerSettings,
438
370
  suppressPreSubmitCancelledWarning: true,
439
371
  thinkingControl: showComposerSettings,
@@ -448,10 +380,10 @@ function ChatFirstPublicShell({
448
380
  ...publicShell?.emptyState
449
381
  },
450
382
  suggestions: publicShell?.suggestions ?? defaultPublicSuggestions,
451
- commands: publicCommands,
383
+ commands: [],
452
384
  excludeBuiltinCommands: builtinCommands.map((command) => command.name),
453
385
  onBeforeSubmit: (draft) => {
454
- if (!runPublicCommand(draft)) openAuth(draft);
386
+ openAuth(draft);
455
387
  return false;
456
388
  }
457
389
  }
@@ -563,7 +495,7 @@ function usePendingChatDraft() {
563
495
  const session = useSession();
564
496
  const userId = session.data?.user?.id ?? null;
565
497
  const [pending, setPending] = useState3(() => userId ? readPendingChatEntry() : null);
566
- useEffect3(() => {
498
+ useEffect2(() => {
567
499
  if (!userId) {
568
500
  setPending(null);
569
501
  return;
@@ -1004,7 +936,7 @@ function isPaymentRequiredNotice(notice) {
1004
936
  }
1005
937
 
1006
938
  // src/app/front/credits/useCreditBalance.ts
1007
- import { useCallback, useEffect as useEffect4, useRef as useRef2, useState as useState4 } from "react";
939
+ import { useCallback, useEffect as useEffect3, useRef, useState as useState4 } from "react";
1008
940
  var CREDITS_REFRESH_EVENT = "credits:refresh";
1009
941
  var CHECKOUT_BASELINE_STORAGE_KEY = "credits:checkout-baseline";
1010
942
  var CHECKOUT_BASELINE_TTL_MS = 60 * 60 * 1e3;
@@ -1020,11 +952,11 @@ function useCreditBalance({
1020
952
  const [lastUpdatedAt, setLastUpdatedAt] = useState4(null);
1021
953
  const [updating, setUpdating] = useState4(false);
1022
954
  const [error, setError] = useState4(null);
1023
- const buyingRef = useRef2(false);
1024
- const burstRef = useRef2(0);
1025
- const timersRef = useRef2([]);
1026
- const burstActiveRef = useRef2(false);
1027
- const balanceRef = useRef2(null);
955
+ const buyingRef = useRef(false);
956
+ const burstRef = useRef(0);
957
+ const timersRef = useRef([]);
958
+ const burstActiveRef = useRef(false);
959
+ const balanceRef = useRef(null);
1028
960
  const refresh = useCallback(async () => {
1029
961
  setUpdating(true);
1030
962
  try {
@@ -1073,7 +1005,7 @@ function useCreditBalance({
1073
1005
  }, delay)
1074
1006
  );
1075
1007
  }, [refresh]);
1076
- useEffect4(() => {
1008
+ useEffect3(() => {
1077
1009
  void refresh();
1078
1010
  const interval = setInterval(() => void refresh(), pollMs);
1079
1011
  const onFocus = () => void refresh();
@@ -1403,7 +1335,7 @@ function CreditsSettingsPanel({ apiBaseUrl = "", locale }) {
1403
1335
  }
1404
1336
 
1405
1337
  // src/app/front/credits/useCheckoutReturnHandler.ts
1406
- import { useEffect as useEffect5, useState as useState8 } from "react";
1338
+ import { useEffect as useEffect4, useState as useState8 } from "react";
1407
1339
  var CONFIRM_SCHEDULE_MS = [0, 1500, 3e3, 6e3, 1e4, 15e3, 22e3, 3e4, 45e3, 6e4];
1408
1340
  async function fetchBalance(apiBaseUrl) {
1409
1341
  try {
@@ -1437,7 +1369,7 @@ function takeStoredBaseline(now) {
1437
1369
  }
1438
1370
  function useCheckoutReturnHandler({ apiBaseUrl = "", param = "checkout" } = {}) {
1439
1371
  const [status, setStatus] = useState8("idle");
1440
- useEffect5(() => {
1372
+ useEffect4(() => {
1441
1373
  if (typeof window === "undefined") return;
1442
1374
  const url = new URL(window.location.href);
1443
1375
  const marker = url.searchParams.get(param);
@@ -983,6 +983,7 @@ async function startCoreWorkspaceAgentDevServer({
983
983
  proxy: {
984
984
  "/api": apiTarget,
985
985
  "/health": apiTarget,
986
+ "/dev-login": apiTarget,
986
987
  "/auth": {
987
988
  target: apiTarget,
988
989
  changeOrigin: true,