@hachej/boring-core 0.1.52 → 0.1.54

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.
@@ -267,3 +267,24 @@
267
267
  font-weight: 600;
268
268
  color: color-mix(in oklch, var(--foreground) 88%, transparent);
269
269
  }
270
+
271
+ /* Optional marketing/nav links rendered beside the brand in the public top bar
272
+ (ChatFirstPublicShellOptions.navLinks). The authed workspace is unaffected. */
273
+ .public-topbar-nav {
274
+ display: flex;
275
+ align-items: center;
276
+ gap: 1rem;
277
+ margin-left: 0.75rem;
278
+ }
279
+
280
+ .public-topbar-nav a {
281
+ font-size: 0.8125rem;
282
+ font-weight: 500;
283
+ text-decoration: none;
284
+ color: color-mix(in oklch, var(--foreground) 65%, transparent);
285
+ transition: color 0.15s ease;
286
+ }
287
+
288
+ .public-topbar-nav a:hover {
289
+ color: var(--foreground);
290
+ }
@@ -33,6 +33,15 @@ interface ChatFirstPublicShellOptions {
33
33
  * otherwise the fixed-position arrows overlay the open panel. Defaults to on.
34
34
  */
35
35
  showTeachingArrows?: boolean;
36
+ /**
37
+ * Marketing/navigation links rendered in the public (no-auth) top bar next to
38
+ * the brand — e.g. About, Pricing, Docs. The authenticated workspace top bar
39
+ * is unaffected. Use plain hrefs (router or full-page routes both work).
40
+ */
41
+ navLinks?: Array<{
42
+ label: string;
43
+ href: string;
44
+ }>;
36
45
  }
37
46
 
38
47
  type ChatEntryMode = 'auth-first' | 'chat-first';
@@ -57,7 +66,7 @@ interface CoreWorkspaceAgentFrontProps<TSession extends WorkspaceAgentSession =
57
66
  /** Default top-bar right content. Exported so apps can compose extra widgets
58
67
  * (e.g. a credit balance badge) alongside the user menu. */
59
68
  declare function DefaultTopBarRight(): react_jsx_runtime.JSX.Element;
60
- declare function CoreWorkspaceAgentFront<TSession extends WorkspaceAgentSession = WorkspaceAgentSession>({ authPages, cspNonce, children, workspaceRoute, workspaceIdParam, workspaceHref, loadingFallback, bootPreloadPaths, topBarLeft, topBarRight, appTitle, bridgeEndpoint, hotReload, chatEntryMode, chatFirstPublicShell, chatFirstPublicWorkspaceProps, publicPaths, ...workspaceProps }: CoreWorkspaceAgentFrontProps<TSession>): react_jsx_runtime.JSX.Element;
69
+ declare function CoreWorkspaceAgentFront<TSession extends WorkspaceAgentSession = WorkspaceAgentSession>({ authPages, cspNonce, children, workspaceRoute, workspaceIdParam, workspaceHref, loadingFallback, bootPreloadPaths, topBarLeft, topBarRight, appTitle, bridgeEndpoint, fullPageBasePath, hotReload, chatEntryMode, chatFirstPublicShell, chatFirstPublicWorkspaceProps, publicPaths, ...workspaceProps }: CoreWorkspaceAgentFrontProps<TSession>): react_jsx_runtime.JSX.Element;
61
70
 
62
71
  interface CreditBalanceBadgeProps {
63
72
  /** Base URL for the credits API (default: same origin). */
@@ -9,7 +9,7 @@ import {
9
9
  useSignIn,
10
10
  useSignUp,
11
11
  useWorkspaceRouteStatus
12
- } from "../../chunk-2ZTKRLVG.js";
12
+ } from "../../chunk-Q53N4QUK.js";
13
13
  import "../../chunk-HYNKZSTF.js";
14
14
  import {
15
15
  isRuntimeEmailVerificationEnabled
@@ -20,8 +20,13 @@ import "../../chunk-MLKGABMK.js";
20
20
  // src/app/front/CoreWorkspaceAgentFront.tsx
21
21
  import { useEffect as useEffect3, useMemo, useState as useState3 } from "react";
22
22
  import { Navigate, Route, useLocation as useLocation2, useParams } from "react-router-dom";
23
+ import { WorkspaceProvider } from "@hachej/boring-workspace";
24
+ import { ErrorState } from "@hachej/boring-ui-kit";
23
25
  import {
24
- WorkspaceAgentFront as WorkspaceAgentFront2
26
+ parseFullPagePanelLocation,
27
+ WorkspaceAgentFront as WorkspaceAgentFront2,
28
+ WorkspaceBootGate,
29
+ WorkspaceFullPagePanel
25
30
  } from "@hachej/boring-workspace/app/front";
26
31
 
27
32
  // src/app/front/chatFirst/ChatFirstAuthenticatedShell.tsx
@@ -420,7 +425,8 @@ function ChatFirstPublicShell({
420
425
  children: (appTitle?.[0] ?? "B").toUpperCase()
421
426
  }
422
427
  ),
423
- /* @__PURE__ */ jsx4("span", { className: "truncate text-[13px] font-medium leading-none tracking-tight text-foreground", children: appTitle })
428
+ /* @__PURE__ */ jsx4("span", { className: "truncate text-[13px] font-medium leading-none tracking-tight text-foreground", children: appTitle }),
429
+ publicShell?.navLinks?.length ? /* @__PURE__ */ jsx4("nav", { className: "public-topbar-nav", "aria-label": "Site", children: publicShell.navLinks.map((link) => /* @__PURE__ */ jsx4("a", { href: link.href, children: link.label }, link.href)) }) : null
424
430
  ] }),
425
431
  className: workspaceProps.className,
426
432
  surfaceButtonBottomOffset: 456,
@@ -503,6 +509,7 @@ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-run
503
509
  installVitePreloadRecovery();
504
510
  var DEFAULT_WORKSPACE_ROUTE = "/workspace/:id";
505
511
  var DEFAULT_WORKSPACE_ID_PARAM = "id";
512
+ var DEFAULT_FULL_PAGE_BASE_PATH = "/full-page";
506
513
  function DefaultTopBarRight() {
507
514
  return /* @__PURE__ */ jsx5(UserMenu, {});
508
515
  }
@@ -689,6 +696,10 @@ function WorkspaceRoute({
689
696
  () => ({ ...resolvedWorkspaceProps.authHeaders, "x-boring-workspace-id": workspaceId }),
690
697
  [workspaceId, resolvedWorkspaceProps.authHeaders]
691
698
  );
699
+ const scopedFullPageBasePath = useMemo(
700
+ () => resolvedWorkspaceProps.fullPageBasePath ? withWorkspaceIdSearch(resolvedWorkspaceProps.fullPageBasePath, workspaceId) : void 0,
701
+ [resolvedWorkspaceProps.fullPageBasePath, workspaceId]
702
+ );
692
703
  if (!workspaceId) return /* @__PURE__ */ jsx5(Fragment3, { children: resolvedLoadingFallback });
693
704
  if (!session.data?.user && chatEntryMode === "chat-first") {
694
705
  return /* @__PURE__ */ jsx5(
@@ -736,6 +747,7 @@ function WorkspaceRoute({
736
747
  workspaceLabel: resolvedWorkspaceProps.workspaceLabel ?? currentWorkspace.name,
737
748
  requestHeaders,
738
749
  authHeaders,
750
+ fullPageBasePath: scopedFullPageBasePath,
739
751
  chatParams,
740
752
  bootPreloadPaths,
741
753
  frontPluginHotReload: false,
@@ -745,6 +757,110 @@ function WorkspaceRoute({
745
757
  workspaceId
746
758
  );
747
759
  }
760
+ function fullPageRoutePath(basePath) {
761
+ const path = basePath.split(/[?#]/, 1)[0]?.trim();
762
+ return path || DEFAULT_FULL_PAGE_BASE_PATH;
763
+ }
764
+ function workspaceIdFromFullPageSearch(search) {
765
+ const workspaceId = new URLSearchParams(search).get("workspaceId")?.trim();
766
+ return workspaceId || null;
767
+ }
768
+ function withWorkspaceIdSearch(basePath, workspaceId) {
769
+ const [pathWithSearch, hash = ""] = basePath.split("#", 2);
770
+ const [path, rawSearch = ""] = pathWithSearch.split("?", 2);
771
+ const search = new URLSearchParams(rawSearch);
772
+ search.set("workspaceId", workspaceId);
773
+ return `${path}?${search.toString()}${hash ? `#${hash}` : ""}`;
774
+ }
775
+ function scopedWorkspaceHeaders(workspaceId, headers) {
776
+ return { ...headers ?? {}, "x-boring-workspace-id": workspaceId };
777
+ }
778
+ function FullPageRouteErrorPage({ code, title, description }) {
779
+ return /* @__PURE__ */ jsx5(
780
+ "div",
781
+ {
782
+ className: "flex min-h-screen items-center justify-center bg-background p-6 text-foreground",
783
+ "data-testid": "full-page-error-state",
784
+ "data-full-page-error-code": code,
785
+ children: /* @__PURE__ */ jsx5(ErrorState, { className: "w-full max-w-lg", title, description })
786
+ }
787
+ );
788
+ }
789
+ function CoreFullPagePanelRoute({
790
+ fullPageBasePath,
791
+ loadingFallback,
792
+ bootPreloadPaths,
793
+ workspaceProps,
794
+ appTitle
795
+ }) {
796
+ const location = useLocation2();
797
+ const parsed = useMemo(() => parseFullPagePanelLocation(location.search), [location.search]);
798
+ const currentWorkspace = useCurrentWorkspace();
799
+ const workspaceId = workspaceIdFromFullPageSearch(location.search) ?? currentWorkspace?.id ?? "";
800
+ const scopedFullPageBasePath = workspaceId ? withWorkspaceIdSearch(fullPageBasePath, workspaceId) : fullPageBasePath;
801
+ const requestHeaders = workspaceId ? scopedWorkspaceHeaders(workspaceId, workspaceProps.requestHeaders) : workspaceProps.requestHeaders;
802
+ const authHeaders = workspaceId ? scopedWorkspaceHeaders(workspaceId, { ...workspaceProps.requestHeaders ?? {}, ...workspaceProps.authHeaders ?? {} }) : { ...workspaceProps.requestHeaders ?? {}, ...workspaceProps.authHeaders ?? {} };
803
+ if (parsed.error || !parsed.componentId) {
804
+ return /* @__PURE__ */ jsx5(
805
+ FullPageRouteErrorPage,
806
+ {
807
+ code: parsed.error?.code ?? "FULL_PAGE_PANEL_MISSING_COMPONENT",
808
+ title: "Invalid full-page panel route",
809
+ description: parsed.error?.message ?? "Missing full-page panel component id."
810
+ }
811
+ );
812
+ }
813
+ if (!workspaceId) {
814
+ return /* @__PURE__ */ jsx5(Fragment3, { children: loadingFallback ?? /* @__PURE__ */ jsx5(
815
+ FullPageRouteErrorPage,
816
+ {
817
+ code: "FULL_PAGE_PANEL_MISSING_WORKSPACE",
818
+ title: "Workspace unavailable",
819
+ description: "The full-page panel route needs a workspace id. Open it from a workspace or include workspaceId in the URL."
820
+ }
821
+ ) });
822
+ }
823
+ return /* @__PURE__ */ jsx5(
824
+ WorkspaceProvider,
825
+ {
826
+ chatPanel: workspaceProps.chatPanel,
827
+ plugins: workspaceProps.plugins,
828
+ excludeDefaults: workspaceProps.excludeDefaults,
829
+ panels: workspaceProps.panels,
830
+ commands: workspaceProps.commands,
831
+ catalogs: workspaceProps.catalogs,
832
+ capabilities: workspaceProps.capabilities,
833
+ apiBaseUrl: workspaceProps.apiBaseUrl,
834
+ authHeaders,
835
+ apiTimeout: workspaceProps.apiTimeout,
836
+ defaultTheme: workspaceProps.defaultTheme,
837
+ onThemeChange: workspaceProps.onThemeChange,
838
+ workspaceId,
839
+ workspaceLabel: currentWorkspace?.id === workspaceId ? currentWorkspace.name : workspaceProps.workspaceLabel,
840
+ appTitle,
841
+ storageKey: workspaceProps.providerStorageKey ?? `boring-ui-v2:layout:${workspaceId}`,
842
+ persistenceEnabled: workspaceProps.persistenceEnabled,
843
+ manageDocumentTitle: false,
844
+ bridgeEndpoint: null,
845
+ onAuthError: workspaceProps.onAuthError,
846
+ onOpenFile: workspaceProps.onOpenFile,
847
+ debug: workspaceProps.debug,
848
+ frontPluginHotReload: false,
849
+ fullPageBasePath: scopedFullPageBasePath,
850
+ children: /* @__PURE__ */ jsx5(
851
+ WorkspaceBootGate,
852
+ {
853
+ workspaceId,
854
+ requestHeaders,
855
+ apiBaseUrl: workspaceProps.apiBaseUrl,
856
+ preloadPaths: bootPreloadPaths,
857
+ provisionWorkspace: workspaceProps.provisionWorkspace,
858
+ children: /* @__PURE__ */ jsx5(WorkspaceFullPagePanel, { componentId: parsed.componentId, params: parsed.params })
859
+ }
860
+ )
861
+ }
862
+ );
863
+ }
748
864
  function chatFirstPublicPaths(workspaceRoute) {
749
865
  return Array.from(/* @__PURE__ */ new Set(["/", workspaceRoute, "/workspace/:id", "/w/:id"]));
750
866
  }
@@ -761,6 +877,7 @@ function CoreWorkspaceAgentFront({
761
877
  topBarRight = /* @__PURE__ */ jsx5(DefaultTopBarRight, {}),
762
878
  appTitle,
763
879
  bridgeEndpoint = "/api/v1/ui",
880
+ fullPageBasePath = DEFAULT_FULL_PAGE_BASE_PATH,
764
881
  hotReload = false,
765
882
  chatEntryMode = "auth-first",
766
883
  chatFirstPublicShell,
@@ -775,7 +892,8 @@ function CoreWorkspaceAgentFront({
775
892
  }
776
893
  const routedWorkspaceProps = {
777
894
  ...workspaceProps,
778
- bridgeEndpoint
895
+ bridgeEndpoint,
896
+ fullPageBasePath
779
897
  };
780
898
  return /* @__PURE__ */ jsxs4(
781
899
  CoreFront,
@@ -828,6 +946,22 @@ function CoreWorkspaceAgentFront({
828
946
  )
829
947
  }
830
948
  ),
949
+ /* @__PURE__ */ jsx5(
950
+ Route,
951
+ {
952
+ path: fullPageRoutePath(fullPageBasePath),
953
+ element: /* @__PURE__ */ jsx5(
954
+ CoreFullPagePanelRoute,
955
+ {
956
+ fullPageBasePath,
957
+ loadingFallback,
958
+ bootPreloadPaths,
959
+ workspaceProps: routedWorkspaceProps,
960
+ appTitle
961
+ }
962
+ )
963
+ }
964
+ ),
831
965
  children
832
966
  ]
833
967
  }
@@ -1463,15 +1463,25 @@ function SettingsTopBar() {
1463
1463
  className: "relative flex h-[52px] items-center justify-between gap-3 border-b border-border/40 bg-background px-4",
1464
1464
  "aria-label": "App top bar",
1465
1465
  children: /* @__PURE__ */ jsxs6("div", { className: "flex min-w-0 flex-1 items-center gap-2.5", children: [
1466
- /* @__PURE__ */ jsx13(
1467
- "div",
1466
+ /* @__PURE__ */ jsxs6(
1467
+ "a",
1468
1468
  {
1469
- "aria-hidden": "true",
1470
- className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-foreground text-[12px] font-semibold text-background",
1471
- children: appInitial
1469
+ href: "/",
1470
+ "aria-label": `${appTitle} home`,
1471
+ className: "flex min-w-0 items-center gap-2.5 rounded-md outline-none transition-opacity hover:opacity-80 focus-visible:ring-2 focus-visible:ring-ring",
1472
+ children: [
1473
+ /* @__PURE__ */ jsx13(
1474
+ "div",
1475
+ {
1476
+ "aria-hidden": "true",
1477
+ className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-foreground text-[12px] font-semibold text-background",
1478
+ children: appInitial
1479
+ }
1480
+ ),
1481
+ /* @__PURE__ */ jsx13("span", { className: "truncate text-[13px] font-medium tracking-tight text-foreground", children: appTitle })
1482
+ ]
1472
1483
  }
1473
1484
  ),
1474
- /* @__PURE__ */ jsx13("span", { className: "truncate text-[13px] font-medium tracking-tight text-foreground", children: appTitle }),
1475
1485
  /* @__PURE__ */ jsx13("span", { "aria-hidden": "true", className: "text-muted-foreground/30", children: "/" }),
1476
1486
  /* @__PURE__ */ jsx13("span", { className: "truncate text-[13px] text-muted-foreground", children: "Account settings" })
1477
1487
  ] })
@@ -58,7 +58,7 @@ import {
58
58
  useWorkspaceMembers,
59
59
  useWorkspaceRole,
60
60
  useWorkspaceRouteStatus
61
- } from "../chunk-2ZTKRLVG.js";
61
+ } from "../chunk-Q53N4QUK.js";
62
62
  import {
63
63
  TopBarSlotProvider,
64
64
  useTopBarSlot
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-core",
3
- "version": "0.1.52",
3
+ "version": "0.1.54",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Foundation package for boring-ui-v2 apps: DB, auth, config, HTTP app factory, and frontend app shell.",
@@ -79,9 +79,9 @@
79
79
  "react-router-dom": "^7.14.2",
80
80
  "smol-toml": "^1.6.1",
81
81
  "zod": "^3.25.76",
82
- "@hachej/boring-agent": "0.1.52",
83
- "@hachej/boring-workspace": "0.1.52",
84
- "@hachej/boring-ui-kit": "0.1.52"
82
+ "@hachej/boring-ui-kit": "0.1.54",
83
+ "@hachej/boring-workspace": "0.1.54",
84
+ "@hachej/boring-agent": "0.1.54"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@testing-library/jest-dom": "^6.9.1",