@tribe-nest/forge 3.19.0 → 3.20.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tribe-nest/forge",
3
- "version": "3.19.0",
3
+ "version": "3.20.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -5,11 +5,12 @@ import { ForgeAnalytics } from "../analytics/ForgeAnalytics";
5
5
  import { PwaRegistration } from "../styled/PwaRegistration";
6
6
  import { InstallBanner } from "../styled/InstallBanner";
7
7
  import { CookieConsent } from "../styled/CookieConsent";
8
+ import { AiAgentWidget } from "../styled/AiAgentWidget";
8
9
  import { useThemeTokens } from "../theme/ForgeThemeProvider";
9
10
  import { useInitialSiteConfig } from "../../provider/SiteConfigProvider";
10
11
  import { PoweredBy } from "./PoweredBy";
11
12
  import { PreviewDiagnostics } from "./PreviewDiagnostics";
12
- import { shellPoweredByEnabled, shellPwaEnabled } from "./shellGating";
13
+ import { shellAiAgentEnabled, shellPoweredByEnabled, shellPwaEnabled } from "./shellGating";
13
14
  import { previewDiagnosticsEnabled, resolvePreviewDiagnostics } from "./diagnosticsGating";
14
15
  import type { ForgeSsrDiagnostics } from "../../types/diagnostics";
15
16
  import { captureAttributionRefFromUrl, readAttributionRef } from "../../utils/attribution";
@@ -52,6 +53,14 @@ export interface TribeNestAppProps extends Omit<ForgeProviderProps, "children">
52
53
  analytics?: boolean;
53
54
  pwa?: boolean;
54
55
  cookieConsent?: boolean;
56
+ /**
57
+ * The website AI agent bubble. Defaults to on for a fan site and off for a
58
+ * mini-app; set it explicitly to override either default. Renders nothing
59
+ * unless the creator has also enabled the agent for the profile — the widget
60
+ * fetches its own config and self-hides. For custom chat UI, leave this off
61
+ * and build on the `useAiAgent()` hook instead.
62
+ */
63
+ aiAgent?: boolean;
55
64
  }
56
65
 
57
66
  // Ensure the PWA `<link rel="manifest">` + a `theme-color` meta exist even if the
@@ -114,6 +123,7 @@ export function TribeNestApp({
114
123
  analytics = true,
115
124
  pwa = true,
116
125
  cookieConsent = true,
126
+ aiAgent,
117
127
  ...forgeProps
118
128
  }: TribeNestAppProps) {
119
129
  // Register the SW + offer install only on the live published site — never in
@@ -154,6 +164,11 @@ export function TribeNestApp({
154
164
  {/* Consent banner — shown on first visit, re-openable from anywhere via
155
165
  useCookieConsent().reopen(). Hidden in the editor. */}
156
166
  {cookieConsent && !editable && <CookieConsent useTribeNestPrivacy />}
167
+ {/* Website AI agent — floating bubble, self-hiding when the profile has no
168
+ agent enabled. Lives here rather than in each __root so enabling the
169
+ agent in admin is enough to make it appear; before this it shipped only
170
+ on the Craft stack and no code site ever rendered it. */}
171
+ {shellAiAgentEnabled({ aiAgent, editable, appId: forgeProps.appId }) && <AiAgentWidget />}
157
172
  </ForgeProvider>
158
173
  );
159
174
  }
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { poweredByHref, shellPoweredByEnabled, shellPwaEnabled } from "./shellGating";
2
+ import { poweredByHref, shellAiAgentEnabled, shellPoweredByEnabled, shellPwaEnabled } from "./shellGating";
3
3
 
4
4
  // The PWA (SW register + install prompt) must be ON only for the live published
5
5
  // site, and OFF everywhere else (editor, preview/draft), so review Workers never
@@ -47,6 +47,31 @@ describe("shellPoweredByEnabled", () => {
47
47
  });
48
48
  });
49
49
 
50
+ // The agent bubble defaults ON for fan sites and OFF for mini-apps, and is
51
+ // blocked in the editor because an answer there spends the creator's AI credits.
52
+ // The widget itself still renders nothing when the agent is disabled for the
53
+ // profile — this gate is about the SURFACE, not the entitlement.
54
+ describe("shellAiAgentEnabled", () => {
55
+ it("is on by default for a fan site, live or preview", () => {
56
+ expect(shellAiAgentEnabled({ editable: false })).toBe(true);
57
+ expect(shellAiAgentEnabled({})).toBe(true);
58
+ });
59
+
60
+ it("is off by default for a mini-app", () => {
61
+ expect(shellAiAgentEnabled({ editable: false, appId: "app-7" })).toBe(false);
62
+ });
63
+
64
+ it("is off in the editor even when explicitly asked for", () => {
65
+ expect(shellAiAgentEnabled({ aiAgent: true, editable: true })).toBe(false);
66
+ expect(shellAiAgentEnabled({ editable: true })).toBe(false);
67
+ });
68
+
69
+ it("lets an explicit prop override the per-surface default in both directions", () => {
70
+ expect(shellAiAgentEnabled({ aiAgent: false, editable: false })).toBe(false); // site opts out
71
+ expect(shellAiAgentEnabled({ aiAgent: true, editable: false, appId: "app-7" })).toBe(true); // app opts in
72
+ });
73
+ });
74
+
50
75
  // The badge is only worth carrying if we can tell WHICH site sent the visitor,
51
76
  // so the identifying params are the point of the link, not decoration.
52
77
  describe("poweredByHref", () => {
@@ -24,6 +24,22 @@ export function shellPoweredByEnabled(opts: {
24
24
  return !opts.hideBadge && !opts.editable && opts.state === "published";
25
25
  }
26
26
 
27
+ // The website AI agent bubble. Unlike the PWA it IS wanted on preview/draft
28
+ // deploys — that is where a creator checks the assistant before going live — so
29
+ // the only hard block is the HMR editor, where a live answer would spend the
30
+ // creator's AI credits every time they typed into their own site.
31
+ //
32
+ // The default differs by surface. A fan site gets it (the agent answers from the
33
+ // artist's content + knowledge base, which is what a visitor is there for); a
34
+ // mini-app does NOT, because an app is a utility and a floating "ask me about
35
+ // the artist" bubble is noise on top of it. `appId` is baked at build time, so
36
+ // this is decided without a runtime lookup. An explicit `aiAgent` prop wins
37
+ // either way — that is how an app opts in, or a site opts out.
38
+ export function shellAiAgentEnabled(opts: { aiAgent?: boolean; editable?: boolean; appId?: string }): boolean {
39
+ if (opts.editable) return false;
40
+ return opts.aiAgent ?? !opts.appId;
41
+ }
42
+
27
43
  /** Marketing site the badge points at. */
28
44
  const TRIBENEST_URL = "https://tribenest.co/";
29
45