@hachej/boring-core 0.1.22 → 0.1.24

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.
@@ -14,7 +14,10 @@ interface CoreFrontProps {
14
14
  children?: ReactNode;
15
15
  authPages?: CoreFrontAuthPagesOverride;
16
16
  cspNonce?: string;
17
+ workspaceRoute?: string;
18
+ workspaceIdParam?: string;
19
+ publicPaths?: string[];
17
20
  }
18
- declare function CoreFront({ children, authPages, cspNonce }: CoreFrontProps): react_jsx_runtime.JSX.Element;
21
+ declare function CoreFront({ children, authPages, cspNonce, workspaceRoute, workspaceIdParam, publicPaths }: CoreFrontProps): react_jsx_runtime.JSX.Element;
19
22
 
20
23
  export { type CoreFrontAuthPagesOverride as C, CoreFront as a, type CoreFrontProps as b };
@@ -1,11 +1,13 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { C as CoreFrontAuthPagesOverride } from '../../CoreFront-CgAkiEts.js';
3
+ import { C as CoreFrontAuthPagesOverride } from '../../CoreFront-N0QJSYaM.js';
4
4
  import { WorkspaceAgentSession, WorkspaceAgentFrontProps } from '@hachej/boring-workspace/app/front';
5
5
 
6
+ type ChatEntryMode = 'auth-first' | 'chat-first';
6
7
  interface CoreWorkspaceAgentFrontProps<TSession extends WorkspaceAgentSession = WorkspaceAgentSession> extends Omit<WorkspaceAgentFrontProps<TSession>, 'workspaceId' | 'frontPluginHotReload' | 'hotReloadEnabled'> {
7
8
  /** Core consumes plugins statically for now; app-level hot reload is explicitly unsupported. */
8
9
  hotReload?: false;
10
+ chatEntryMode?: ChatEntryMode;
9
11
  authPages?: CoreFrontAuthPagesOverride;
10
12
  cspNonce?: string;
11
13
  children?: ReactNode;
@@ -15,6 +17,6 @@ interface CoreWorkspaceAgentFrontProps<TSession extends WorkspaceAgentSession =
15
17
  loadingFallback?: ReactNode;
16
18
  bootPreloadPaths?: string[];
17
19
  }
18
- declare function CoreWorkspaceAgentFront<TSession extends WorkspaceAgentSession = WorkspaceAgentSession>({ authPages, cspNonce, children, workspaceRoute, workspaceIdParam, workspaceHref, loadingFallback, bootPreloadPaths, topBarLeft, topBarRight, appTitle, bridgeEndpoint, hotReload, ...workspaceProps }: CoreWorkspaceAgentFrontProps<TSession>): react_jsx_runtime.JSX.Element;
20
+ declare function CoreWorkspaceAgentFront<TSession extends WorkspaceAgentSession = WorkspaceAgentSession>({ authPages, cspNonce, children, workspaceRoute, workspaceIdParam, workspaceHref, loadingFallback, bootPreloadPaths, topBarLeft, topBarRight, appTitle, bridgeEndpoint, hotReload, chatEntryMode, ...workspaceProps }: CoreWorkspaceAgentFrontProps<TSession>): react_jsx_runtime.JSX.Element;
19
21
 
20
22
  export { CoreWorkspaceAgentFront, type CoreWorkspaceAgentFrontProps };
@@ -2,69 +2,408 @@ import {
2
2
  CoreFront,
3
3
  UserMenu,
4
4
  WorkspaceSwitcher,
5
- useCurrentWorkspace
6
- } from "../../chunk-JMCBLJ6W.js";
5
+ routes,
6
+ useCurrentWorkspace,
7
+ useSession,
8
+ useSignIn,
9
+ useSignUp,
10
+ useWorkspaceRouteStatus
11
+ } from "../../chunk-5R3U6QKD.js";
7
12
  import "../../chunk-HYNKZSTF.js";
8
13
  import "../../chunk-H5KU6R6Y.js";
9
14
  import "../../chunk-MLKGABMK.js";
10
15
 
11
16
  // src/app/front/CoreWorkspaceAgentFront.tsx
12
- import { useMemo } from "react";
13
- import { Navigate, Route, useParams } from "react-router-dom";
17
+ import { useEffect as useEffect2, useMemo, useState as useState3 } from "react";
18
+ import { Navigate, Route, useLocation as useLocation2, useParams } from "react-router-dom";
14
19
  import {
15
- WorkspaceAgentFront,
16
- WorkspaceBootGate
20
+ WorkspaceAgentFront as WorkspaceAgentFront2
17
21
  } from "@hachej/boring-workspace/app/front";
22
+
23
+ // src/app/front/chatFirst/ChatFirstAuthenticatedShell.tsx
24
+ import { useEffect } from "react";
25
+ import {
26
+ WorkspaceAgentFront
27
+ } from "@hachej/boring-workspace/app/front";
28
+ import { useWorkspaceAttention } from "@hachej/boring-workspace";
18
29
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
30
+ function ChatFirstComposerBlocker() {
31
+ const { addBlocker, removeBlocker } = useWorkspaceAttention();
32
+ useEffect(() => {
33
+ const blocker = {
34
+ id: "chat-first-workspace-preparing",
35
+ reason: "workspace-preparing",
36
+ label: "Preparing workspace\u2026 Send will work in a moment."
37
+ };
38
+ addBlocker(blocker);
39
+ return () => removeBlocker(blocker.id);
40
+ }, [addBlocker, removeBlocker]);
41
+ return null;
42
+ }
43
+ function ChatFirstAuthenticatedShell({
44
+ appTitle,
45
+ workspaceId,
46
+ initialDraft,
47
+ autoSubmitInitialDraft = false,
48
+ workspaceProps,
49
+ showComposerBlocker = true
50
+ }) {
51
+ return /* @__PURE__ */ jsx(
52
+ WorkspaceAgentFront,
53
+ {
54
+ ...workspaceProps,
55
+ workspaceId,
56
+ appTitle,
57
+ topBarLeft: null,
58
+ sessions: [],
59
+ activeSessionId: null,
60
+ onSwitchSession: () => void 0,
61
+ onCreateSession: () => void 0,
62
+ onDeleteSession: () => void 0,
63
+ provisionWorkspace: false,
64
+ bootPreloadPaths: [],
65
+ bridgeEndpoint: null,
66
+ excludeDefaults: ["filesystem"],
67
+ plugins: [],
68
+ catalogs: [],
69
+ commands: [],
70
+ persistenceEnabled: false,
71
+ navEnabled: false,
72
+ defaultNavOpen: false,
73
+ defaultSurfaceOpen: false,
74
+ beforeShell: showComposerBlocker ? /* @__PURE__ */ jsxs(Fragment, { children: [
75
+ workspaceProps.beforeShell,
76
+ /* @__PURE__ */ jsx(ChatFirstComposerBlocker, {})
77
+ ] }) : workspaceProps.beforeShell,
78
+ chatParams: {
79
+ ...workspaceProps.chatParams,
80
+ composerBlockers: void 0,
81
+ ...initialDraft ? { initialDraft } : {},
82
+ ...initialDraft && autoSubmitInitialDraft ? { autoSubmitInitialDraft: true } : {},
83
+ serverResourcesEnabled: false,
84
+ hydrateMessages: false,
85
+ onBeforeSubmit: showComposerBlocker ? (() => false) : workspaceProps.chatParams?.onBeforeSubmit ?? (() => false)
86
+ },
87
+ frontPluginHotReload: false,
88
+ hotReloadEnabled: false
89
+ }
90
+ );
91
+ }
92
+
93
+ // src/app/front/chatFirst/ChatFirstPublicShell.tsx
94
+ import { useState as useState2 } from "react";
95
+ import { useLocation } from "react-router-dom";
96
+
97
+ // src/app/front/chatFirst/AuthCard.tsx
98
+ import { useState } from "react";
99
+ import { useNavigate } from "react-router-dom";
100
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
101
+ function AuthCard({
102
+ returnTo,
103
+ onClose
104
+ }) {
105
+ const navigate = useNavigate();
106
+ const signIn = useSignIn();
107
+ const signUp = useSignUp();
108
+ const [mode, setMode] = useState("signin");
109
+ const [name, setName] = useState("");
110
+ const [email, setEmail] = useState("");
111
+ const [password, setPassword] = useState("");
112
+ const [error, setError] = useState(null);
113
+ const [submitting, setSubmitting] = useState(false);
114
+ const forgotPasswordHref = `${routes.forgotPassword}?redirect=${encodeURIComponent(returnTo)}`;
115
+ async function submit(event) {
116
+ event.preventDefault();
117
+ setError(null);
118
+ setSubmitting(true);
119
+ try {
120
+ const result = mode === "signin" ? await signIn.email({ email, password }) : await signUp.email({ email, password, name: name || email });
121
+ if (result.error) {
122
+ setError(result.error.message ?? `${mode === "signin" ? "Sign in" : "Sign up"} failed`);
123
+ return;
124
+ }
125
+ onClose?.();
126
+ navigate(returnTo, { replace: true });
127
+ } catch (err) {
128
+ setError(err instanceof Error ? err.message : `${mode === "signin" ? "Sign in" : "Sign up"} failed`);
129
+ } finally {
130
+ setSubmitting(false);
131
+ }
132
+ }
133
+ return /* @__PURE__ */ jsxs2("div", { className: "w-full max-w-sm rounded-2xl border border-border bg-card p-4 shadow-2xl", children: [
134
+ onClose ? /* @__PURE__ */ jsx2("div", { className: "mb-3 flex justify-end", children: /* @__PURE__ */ jsx2("button", { type: "button", className: "rounded-full px-2 py-1 text-sm text-muted-foreground hover:bg-muted", onClick: onClose, "aria-label": "Close sign in", children: "\xD7" }) }) : null,
135
+ /* @__PURE__ */ jsx2("h2", { id: "auth-modal-title", className: "text-center text-xl font-semibold", children: mode === "signin" ? "Sign in to continue" : "Create your account" }),
136
+ /* @__PURE__ */ jsx2("p", { className: "mt-2 text-center text-sm text-muted-foreground", children: "Keep your draft and unlock the full workspace." }),
137
+ /* @__PURE__ */ jsxs2("div", { className: "mt-4 grid grid-cols-2 rounded-xl bg-muted p-1 text-sm", children: [
138
+ /* @__PURE__ */ jsx2("button", { type: "button", className: `rounded-lg px-3 py-2 ${mode === "signin" ? "bg-background shadow-sm" : "text-muted-foreground"}`, onClick: () => setMode("signin"), children: "Sign in" }),
139
+ /* @__PURE__ */ jsx2("button", { type: "button", className: `rounded-lg px-3 py-2 ${mode === "signup" ? "bg-background shadow-sm" : "text-muted-foreground"}`, onClick: () => setMode("signup"), children: "Sign up" })
140
+ ] }),
141
+ /* @__PURE__ */ jsxs2("form", { className: "mt-4 space-y-2.5", onSubmit: submit, children: [
142
+ error ? /* @__PURE__ */ jsx2("div", { className: "rounded-lg border border-destructive/30 bg-destructive/10 p-2 text-sm text-destructive", role: "alert", children: error }) : null,
143
+ mode === "signup" ? /* @__PURE__ */ jsx2("input", { className: "w-full rounded-xl border border-border bg-background px-3 py-2.5 text-sm outline-none focus:border-ring", placeholder: "Name", value: name, onChange: (event) => setName(event.currentTarget.value) }) : null,
144
+ /* @__PURE__ */ jsx2("input", { className: "w-full rounded-xl border border-border bg-background px-3 py-2.5 text-sm outline-none focus:border-ring", type: "email", autoComplete: "email", placeholder: "Email", value: email, onChange: (event) => setEmail(event.currentTarget.value), required: true }),
145
+ /* @__PURE__ */ jsx2("input", { className: "w-full rounded-xl border border-border bg-background px-3 py-2.5 text-sm outline-none focus:border-ring", type: "password", autoComplete: mode === "signin" ? "current-password" : "new-password", placeholder: "Password", value: password, onChange: (event) => setPassword(event.currentTarget.value), required: true }),
146
+ mode === "signin" ? /* @__PURE__ */ jsx2("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx2("a", { href: forgotPasswordHref, className: "text-xs text-muted-foreground hover:underline", children: "Forgot password?" }) }) : null,
147
+ /* @__PURE__ */ jsx2("button", { type: "submit", className: "w-full rounded-xl bg-primary px-3 py-2.5 text-sm font-medium text-primary-foreground disabled:opacity-50", disabled: submitting, children: submitting ? "Please wait\u2026" : mode === "signin" ? "Continue with email" : "Create account" })
148
+ ] }),
149
+ /* @__PURE__ */ jsx2("p", { className: "mt-4 text-center text-xs text-muted-foreground", children: "By continuing, you agree to continue into your private workspace." })
150
+ ] });
151
+ }
152
+
153
+ // src/app/front/chatFirst/AuthModal.tsx
154
+ import { jsx as jsx3 } from "react/jsx-runtime";
155
+ function AuthModal({ onClose, returnTo }) {
156
+ return /* @__PURE__ */ jsx3("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-background/70 p-4 backdrop-blur-md", role: "dialog", "aria-modal": "true", "aria-labelledby": "auth-modal-title", children: /* @__PURE__ */ jsx3(AuthCard, { returnTo, onClose }) });
157
+ }
158
+
159
+ // src/app/front/chatFirst/pendingChatEntry.ts
160
+ import { matchPath } from "react-router-dom";
161
+ var PENDING_CHAT_ENTRY_KEY = "boring:pending-chat-entry";
162
+ var PENDING_CHAT_ENTRY_TTL_MS = 24 * 60 * 60 * 1e3;
163
+ var DEFAULT_CHAT_FIRST_PENDING_WORKSPACE_ID = "pending";
164
+ var PENDING_CHAT_ENTRY_CHANGED_EVENT = "boring:pending-chat-entry-changed";
165
+ function browserSessionStorage() {
166
+ try {
167
+ return typeof window === "undefined" ? null : window.sessionStorage;
168
+ } catch {
169
+ return null;
170
+ }
171
+ }
172
+ function safeReturnTo(pathname, search, hash) {
173
+ const candidate = `${pathname || "/"}${search || ""}${hash || ""}`;
174
+ if (!candidate.startsWith("/") || candidate.startsWith("//") || /[\0\r\n<>"'`]/.test(candidate)) return "/";
175
+ return candidate;
176
+ }
177
+ function readPendingChatEntry() {
178
+ const storage = browserSessionStorage();
179
+ if (!storage) return null;
180
+ try {
181
+ const raw = storage.getItem(PENDING_CHAT_ENTRY_KEY);
182
+ if (!raw) return null;
183
+ const parsed = JSON.parse(raw);
184
+ if (typeof parsed.draft !== "string" || typeof parsed.returnTo !== "string" || typeof parsed.createdAt !== "number") return null;
185
+ if (Date.now() - parsed.createdAt > PENDING_CHAT_ENTRY_TTL_MS) {
186
+ storage.removeItem(PENDING_CHAT_ENTRY_KEY);
187
+ return null;
188
+ }
189
+ return {
190
+ draft: parsed.draft,
191
+ returnTo: parsed.returnTo,
192
+ intendedWorkspaceId: typeof parsed.intendedWorkspaceId === "string" ? parsed.intendedWorkspaceId : void 0,
193
+ createdAt: parsed.createdAt
194
+ };
195
+ } catch {
196
+ return null;
197
+ }
198
+ }
199
+ function notifyPendingChatEntryChanged() {
200
+ globalThis.dispatchEvent?.(new Event(PENDING_CHAT_ENTRY_CHANGED_EVENT));
201
+ }
202
+ function writePendingChatEntry(input) {
203
+ const storage = browserSessionStorage();
204
+ if (!storage) return;
205
+ storage.setItem(PENDING_CHAT_ENTRY_KEY, JSON.stringify({ ...input, createdAt: Date.now() }));
206
+ notifyPendingChatEntryChanged();
207
+ }
208
+ function clearPendingChatEntry() {
209
+ browserSessionStorage()?.removeItem(PENDING_CHAT_ENTRY_KEY);
210
+ notifyPendingChatEntryChanged();
211
+ }
212
+ function pendingChatEntryMatchesLocation(pending, pathname, search, hash) {
213
+ return Boolean(pending && pending.returnTo === safeReturnTo(pathname, search, hash));
214
+ }
215
+ function routePatterns(route) {
216
+ const normalized = route.endsWith("/*") ? route.slice(0, -2) : route;
217
+ return [`${normalized}/*`, normalized];
218
+ }
219
+ function workspaceIdFromPath(pathname, workspaceRoute, workspaceIdParam) {
220
+ const patterns = [
221
+ ...routePatterns(workspaceRoute),
222
+ "/w/:id/*",
223
+ "/w/:id",
224
+ "/workspace/:id/*",
225
+ "/workspace/:id"
226
+ ];
227
+ for (const pattern of patterns) {
228
+ const match = matchPath(pattern, pathname);
229
+ const id = match?.params[workspaceIdParam]?.trim() ?? match?.params.id?.trim();
230
+ if (id) return id;
231
+ }
232
+ return null;
233
+ }
234
+
235
+ // src/app/front/chatFirst/ChatFirstPublicShell.tsx
236
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
237
+ function readComposerDraftFromDom() {
238
+ if (typeof document === "undefined") return "";
239
+ const input = document.querySelector('[data-boring-agent-part="composer-input"]');
240
+ return input?.value ?? "";
241
+ }
242
+ function ChatFirstPublicShell({
243
+ appTitle,
244
+ intendedWorkspaceId,
245
+ workspaceProps
246
+ }) {
247
+ const location = useLocation();
248
+ const [modalOpen, setModalOpen] = useState2(false);
249
+ const returnTo = safeReturnTo(location.pathname, location.search, location.hash);
250
+ const workspaceId = intendedWorkspaceId || "public";
251
+ const openAuth = (draft = readComposerDraftFromDom()) => {
252
+ writePendingChatEntry({ draft, returnTo, ...intendedWorkspaceId ? { intendedWorkspaceId } : {} });
253
+ setModalOpen(true);
254
+ };
255
+ return /* @__PURE__ */ jsxs3("div", { className: "relative h-screen min-h-0", children: [
256
+ /* @__PURE__ */ jsx4(
257
+ ChatFirstAuthenticatedShell,
258
+ {
259
+ appTitle,
260
+ workspaceId,
261
+ showComposerBlocker: false,
262
+ workspaceProps: {
263
+ ...workspaceProps,
264
+ 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" }),
265
+ className: workspaceProps.className,
266
+ surfaceButtonBottomOffset: 456,
267
+ chatParams: {
268
+ ...workspaceProps.chatParams,
269
+ emptyPlacement: "hero",
270
+ composerPlaceholder: "Describe the app, bug, or repo task you want help with\u2026",
271
+ emptyState: {
272
+ eyebrow: "Start here",
273
+ title: "What do you want to build?",
274
+ description: "Type a prompt or pick an example. Sign in on send to unlock your private workspace."
275
+ },
276
+ suggestions: [
277
+ { label: "Build an app from scratch", hint: "Creates files, installs deps, opens a preview", prompt: "Build a full-stack app with auth, a dashboard, and sample data." },
278
+ { label: "Understand a codebase", hint: "Maps the repo and explains where to start", prompt: "Explain this codebase, map the architecture, and suggest first improvements." },
279
+ { label: "Fix a bug safely", hint: "Finds the cause, edits files, runs tests", prompt: "Trace a bug, edit the right files, update tests, and summarize the diff." },
280
+ { label: "Prototype an interface", hint: "Turns an idea into an interactive UI", prompt: "Build an interactive prototype and open it in the workspace." }
281
+ ],
282
+ onBeforeSubmit: (draft) => {
283
+ openAuth(draft);
284
+ return false;
285
+ }
286
+ }
287
+ }
288
+ }
289
+ ),
290
+ /* @__PURE__ */ jsx4("aside", { className: "pointer-events-none fixed bottom-6 right-6 z-20 w-[320px]", children: /* @__PURE__ */ jsx4("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx4(AuthCard, { returnTo }) }) }),
291
+ modalOpen ? /* @__PURE__ */ jsx4(AuthModal, { returnTo, onClose: () => setModalOpen(false) }) : null
292
+ ] });
293
+ }
294
+
295
+ // src/app/front/CoreWorkspaceAgentFront.tsx
296
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
19
297
  var DEFAULT_WORKSPACE_ROUTE = "/workspace/:id";
20
298
  var DEFAULT_WORKSPACE_ID_PARAM = "id";
21
299
  function DefaultTopBarRight() {
22
- return /* @__PURE__ */ jsx(UserMenu, {});
300
+ return /* @__PURE__ */ jsx5(UserMenu, {});
23
301
  }
24
302
  function WorkspaceLoadingPage({
25
303
  appTitle,
26
304
  topBarLeft,
27
305
  topBarRight
28
306
  }) {
29
- return /* @__PURE__ */ jsxs("div", { className: "flex h-screen min-h-0 flex-col bg-background text-foreground", children: [
30
- /* @__PURE__ */ jsxs("header", { className: "flex h-12 shrink-0 items-center justify-between border-b border-border bg-card px-3", children: [
31
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [
32
- /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold", children: appTitle }),
307
+ return /* @__PURE__ */ jsxs4("div", { className: "flex h-screen min-h-0 flex-col bg-background text-foreground", children: [
308
+ /* @__PURE__ */ jsxs4("header", { className: "flex h-12 shrink-0 items-center justify-between border-b border-border bg-card px-3", children: [
309
+ /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 items-center gap-3", children: [
310
+ /* @__PURE__ */ jsx5("div", { className: "text-sm font-semibold", children: appTitle }),
33
311
  topBarLeft
34
312
  ] }),
35
313
  topBarRight
36
314
  ] }),
37
- /* @__PURE__ */ jsx("main", { className: "flex min-h-0 flex-1 items-center justify-center px-6", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-md rounded-2xl border border-border bg-card p-6 text-center shadow-sm", children: [
38
- /* @__PURE__ */ jsx(
315
+ /* @__PURE__ */ jsx5("main", { className: "flex min-h-0 flex-1 items-center justify-center px-6", children: /* @__PURE__ */ jsxs4("div", { className: "w-full max-w-md rounded-2xl border border-border bg-card p-6 text-center shadow-sm", children: [
316
+ /* @__PURE__ */ jsx5(
39
317
  "div",
40
318
  {
41
319
  "aria-hidden": "true",
42
320
  className: "mx-auto mb-4 h-8 w-8 rounded-full border-2 border-muted-foreground/20 border-t-foreground animate-spin will-change-transform"
43
321
  }
44
322
  ),
45
- /* @__PURE__ */ jsx("h1", { className: "text-lg font-semibold", children: "Switching workspace" }),
46
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: "Restoring files, sessions, and saved layout." }),
47
- /* @__PURE__ */ jsx("p", { className: "mt-4 text-xs uppercase tracking-[0.18em] text-muted-foreground", children: "Loading workspace" })
323
+ /* @__PURE__ */ jsx5("h1", { className: "text-lg font-semibold", children: "Switching workspace" }),
324
+ /* @__PURE__ */ jsx5("p", { className: "mt-2 text-sm text-muted-foreground", children: "Restoring files, sessions, and saved layout." }),
325
+ /* @__PURE__ */ jsx5("p", { className: "mt-4 text-xs uppercase tracking-[0.18em] text-muted-foreground", children: "Loading workspace" })
48
326
  ] }) })
49
327
  ] });
50
328
  }
329
+ function usePendingChatDraft() {
330
+ const session = useSession();
331
+ const userId = session.data?.user?.id ?? null;
332
+ const [pending, setPending] = useState3(() => userId ? readPendingChatEntry() : null);
333
+ useEffect2(() => {
334
+ if (!userId) {
335
+ setPending(null);
336
+ return;
337
+ }
338
+ setPending(readPendingChatEntry());
339
+ const syncPending = () => setPending(readPendingChatEntry());
340
+ globalThis.addEventListener?.(PENDING_CHAT_ENTRY_CHANGED_EVENT, syncPending);
341
+ return () => globalThis.removeEventListener?.(PENDING_CHAT_ENTRY_CHANGED_EVENT, syncPending);
342
+ }, [userId]);
343
+ return pending;
344
+ }
51
345
  function HomeRedirect({
52
346
  loadingFallback,
53
- workspaceHref
347
+ workspaceHref,
348
+ chatEntryMode,
349
+ appTitle,
350
+ workspaceProps
54
351
  }) {
352
+ const location = useLocation2();
353
+ const session = useSession();
55
354
  const workspace = useCurrentWorkspace();
56
- if (!workspace) return /* @__PURE__ */ jsx(Fragment, { children: loadingFallback });
57
- return /* @__PURE__ */ jsx(Navigate, { to: workspaceHref(workspace.id), replace: true });
355
+ const pendingChatEntry = usePendingChatDraft();
356
+ const restorePendingDraft = pendingChatEntryMatchesLocation(
357
+ pendingChatEntry,
358
+ location.pathname,
359
+ location.search,
360
+ location.hash
361
+ );
362
+ if (!session.data?.user && chatEntryMode === "chat-first") return /* @__PURE__ */ jsx5(ChatFirstPublicShell, { appTitle, workspaceProps });
363
+ if (!workspace && chatEntryMode === "chat-first" && session.data?.user && restorePendingDraft) {
364
+ return /* @__PURE__ */ jsx5(
365
+ ChatFirstAuthenticatedShell,
366
+ {
367
+ appTitle,
368
+ workspaceId: pendingChatEntry?.intendedWorkspaceId ?? DEFAULT_CHAT_FIRST_PENDING_WORKSPACE_ID,
369
+ initialDraft: pendingChatEntry?.draft,
370
+ workspaceProps
371
+ }
372
+ );
373
+ }
374
+ if (!workspace) return /* @__PURE__ */ jsx5(Fragment2, { children: loadingFallback });
375
+ return /* @__PURE__ */ jsx5(Navigate, { to: workspaceHref(workspace.id), replace: true });
376
+ }
377
+ function WorkspaceRouteErrorPage({ status, message }) {
378
+ const title = status === "not-found" ? "Workspace not found" : status === "forbidden" ? "Workspace unavailable" : "Workspace failed to open";
379
+ return /* @__PURE__ */ jsx5("div", { className: "flex h-screen min-h-0 items-center justify-center bg-background px-6 text-foreground", children: /* @__PURE__ */ jsxs4("div", { className: "w-full max-w-md rounded-2xl border border-border bg-card p-6 text-center shadow-sm", children: [
380
+ /* @__PURE__ */ jsx5("h1", { className: "text-lg font-semibold", children: title }),
381
+ /* @__PURE__ */ jsx5("p", { className: "mt-2 text-sm text-muted-foreground", children: message })
382
+ ] }) });
58
383
  }
59
384
  function WorkspaceRoute({
60
385
  workspaceIdParam,
61
386
  loadingFallback,
62
387
  bootPreloadPaths,
63
- workspaceProps
388
+ workspaceProps,
389
+ chatEntryMode,
390
+ appTitle,
391
+ workspaceRoute
64
392
  }) {
65
393
  const params = useParams();
394
+ const location = useLocation2();
395
+ const session = useSession();
396
+ const pendingChatEntry = usePendingChatDraft();
66
397
  const currentWorkspace = useCurrentWorkspace();
67
- const workspaceId = params[workspaceIdParam]?.trim() ?? "";
398
+ const routeStatus = useWorkspaceRouteStatus();
399
+ const workspaceId = params[workspaceIdParam]?.trim() ?? workspaceIdFromPath(location.pathname, workspaceRoute, workspaceIdParam) ?? "";
400
+ const pendingDraftTargetsWorkspace = !pendingChatEntry?.intendedWorkspaceId || pendingChatEntry.intendedWorkspaceId === workspaceId;
401
+ const restorePendingDraft = pendingDraftTargetsWorkspace && (pendingChatEntryMatchesLocation(
402
+ pendingChatEntry,
403
+ location.pathname,
404
+ location.search,
405
+ location.hash
406
+ ) || pendingChatEntry?.returnTo === "/" && currentWorkspace?.id === workspaceId);
68
407
  const requestHeaders = useMemo(
69
408
  () => ({ ...workspaceProps.requestHeaders, "x-boring-workspace-id": workspaceId }),
70
409
  [workspaceId, workspaceProps.requestHeaders]
@@ -73,30 +412,55 @@ function WorkspaceRoute({
73
412
  () => ({ ...workspaceProps.authHeaders, "x-boring-workspace-id": workspaceId }),
74
413
  [workspaceId, workspaceProps.authHeaders]
75
414
  );
76
- if (!workspaceId) return /* @__PURE__ */ jsx(Fragment, { children: loadingFallback });
77
- if (currentWorkspace?.id !== workspaceId) return /* @__PURE__ */ jsx(Fragment, { children: loadingFallback });
78
- return /* @__PURE__ */ jsx(
79
- WorkspaceBootGate,
415
+ if (!workspaceId) return /* @__PURE__ */ jsx5(Fragment2, { children: loadingFallback });
416
+ if (!session.data?.user && chatEntryMode === "chat-first") {
417
+ return /* @__PURE__ */ jsx5(ChatFirstPublicShell, { appTitle, intendedWorkspaceId: workspaceId, workspaceProps });
418
+ }
419
+ if (routeStatus.status === "not-found" || routeStatus.status === "forbidden" || routeStatus.status === "switch-failed") {
420
+ return /* @__PURE__ */ jsx5(WorkspaceRouteErrorPage, { status: routeStatus.status, message: routeStatus.message });
421
+ }
422
+ if (chatEntryMode === "chat-first" && restorePendingDraft && (routeStatus.status !== "matched" || currentWorkspace?.id !== workspaceId)) {
423
+ return /* @__PURE__ */ jsx5(
424
+ ChatFirstAuthenticatedShell,
425
+ {
426
+ appTitle,
427
+ workspaceId,
428
+ initialDraft: pendingChatEntry?.draft,
429
+ workspaceProps
430
+ }
431
+ );
432
+ }
433
+ if (routeStatus.status !== "matched" || currentWorkspace?.id !== workspaceId) return /* @__PURE__ */ jsx5(Fragment2, { children: loadingFallback });
434
+ const shouldRestorePendingDraft = restorePendingDraft && Boolean(pendingChatEntry?.draft);
435
+ const chatParams = {
436
+ ...workspaceProps.chatParams,
437
+ ...shouldRestorePendingDraft ? { initialDraft: pendingChatEntry?.draft } : {},
438
+ ...shouldRestorePendingDraft ? { autoSubmitInitialDraft: true } : {},
439
+ onBeforeSubmit: async (draft, ctx) => {
440
+ const existing = workspaceProps.chatParams?.onBeforeSubmit;
441
+ const result = await existing?.(draft, ctx);
442
+ if (result !== false) clearPendingChatEntry();
443
+ return result;
444
+ }
445
+ };
446
+ return /* @__PURE__ */ jsx5(
447
+ WorkspaceAgentFront2,
80
448
  {
449
+ ...workspaceProps,
81
450
  workspaceId,
82
451
  requestHeaders,
83
- apiBaseUrl: workspaceProps.apiBaseUrl,
84
- preloadPaths: bootPreloadPaths,
85
- loadingFallback,
86
- children: /* @__PURE__ */ jsx(
87
- WorkspaceAgentFront,
88
- {
89
- ...workspaceProps,
90
- workspaceId,
91
- requestHeaders,
92
- authHeaders,
93
- frontPluginHotReload: false,
94
- hotReloadEnabled: false
95
- }
96
- )
97
- }
452
+ authHeaders,
453
+ chatParams,
454
+ bootPreloadPaths,
455
+ frontPluginHotReload: false,
456
+ hotReloadEnabled: false
457
+ },
458
+ workspaceId
98
459
  );
99
460
  }
461
+ function chatFirstPublicPaths(workspaceRoute) {
462
+ return Array.from(/* @__PURE__ */ new Set(["/", workspaceRoute, "/workspace/:id", "/w/:id"]));
463
+ }
100
464
  function CoreWorkspaceAgentFront({
101
465
  authPages,
102
466
  cspNonce,
@@ -106,11 +470,12 @@ function CoreWorkspaceAgentFront({
106
470
  workspaceHref = (workspaceId) => `/workspace/${workspaceId}`,
107
471
  loadingFallback,
108
472
  bootPreloadPaths,
109
- topBarLeft = /* @__PURE__ */ jsx(WorkspaceSwitcher, {}),
110
- topBarRight = /* @__PURE__ */ jsx(DefaultTopBarRight, {}),
473
+ topBarLeft = /* @__PURE__ */ jsx5(WorkspaceSwitcher, {}),
474
+ topBarRight = /* @__PURE__ */ jsx5(DefaultTopBarRight, {}),
111
475
  appTitle = "Boring",
112
476
  bridgeEndpoint = "/api/v1/ui",
113
477
  hotReload = false,
478
+ chatEntryMode = "auth-first",
114
479
  ...workspaceProps
115
480
  }) {
116
481
  if (hotReload !== false) {
@@ -118,7 +483,7 @@ function CoreWorkspaceAgentFront({
118
483
  "CoreWorkspaceAgentFront does not support hotReload yet; use static plugin consumption or WorkspaceAgentFront for standalone hot reload."
119
484
  );
120
485
  }
121
- const resolvedLoadingFallback = loadingFallback ?? /* @__PURE__ */ jsx(
486
+ const resolvedLoadingFallback = loadingFallback ?? /* @__PURE__ */ jsx5(
122
487
  WorkspaceLoadingPage,
123
488
  {
124
489
  appTitle,
@@ -133,37 +498,53 @@ function CoreWorkspaceAgentFront({
133
498
  topBarRight,
134
499
  bridgeEndpoint
135
500
  };
136
- return /* @__PURE__ */ jsxs(CoreFront, { authPages, cspNonce, children: [
137
- /* @__PURE__ */ jsx(
138
- Route,
139
- {
140
- path: "/",
141
- element: /* @__PURE__ */ jsx(
142
- HomeRedirect,
501
+ return /* @__PURE__ */ jsxs4(
502
+ CoreFront,
503
+ {
504
+ authPages,
505
+ cspNonce,
506
+ workspaceRoute,
507
+ workspaceIdParam,
508
+ publicPaths: chatEntryMode === "chat-first" ? chatFirstPublicPaths(workspaceRoute) : void 0,
509
+ children: [
510
+ /* @__PURE__ */ jsx5(
511
+ Route,
143
512
  {
144
- loadingFallback: resolvedLoadingFallback,
145
- workspaceHref
513
+ path: "/",
514
+ element: /* @__PURE__ */ jsx5(
515
+ HomeRedirect,
516
+ {
517
+ loadingFallback: resolvedLoadingFallback,
518
+ workspaceHref,
519
+ chatEntryMode,
520
+ appTitle,
521
+ workspaceProps: resolvedWorkspaceProps
522
+ }
523
+ )
146
524
  }
147
- )
148
- }
149
- ),
150
- /* @__PURE__ */ jsx(
151
- Route,
152
- {
153
- path: workspaceRoute,
154
- element: /* @__PURE__ */ jsx(
155
- WorkspaceRoute,
525
+ ),
526
+ /* @__PURE__ */ jsx5(
527
+ Route,
156
528
  {
157
- workspaceIdParam,
158
- loadingFallback: resolvedLoadingFallback,
159
- bootPreloadPaths,
160
- workspaceProps: resolvedWorkspaceProps
529
+ path: workspaceRoute,
530
+ element: /* @__PURE__ */ jsx5(
531
+ WorkspaceRoute,
532
+ {
533
+ workspaceIdParam,
534
+ loadingFallback: resolvedLoadingFallback,
535
+ bootPreloadPaths,
536
+ workspaceProps: resolvedWorkspaceProps,
537
+ chatEntryMode,
538
+ appTitle,
539
+ workspaceRoute
540
+ }
541
+ )
161
542
  }
162
- )
163
- }
164
- ),
165
- children
166
- ] });
543
+ ),
544
+ children
545
+ ]
546
+ }
547
+ );
167
548
  }
168
549
  export {
169
550
  CoreWorkspaceAgentFront
@@ -9,7 +9,7 @@ import {
9
9
  registerRoutes,
10
10
  registerSettingsRoutes,
11
11
  registerWorkspaceRoutes
12
- } from "../../chunk-6D7LEQSL.js";
12
+ } from "../../chunk-TAXVSQDW.js";
13
13
  import {
14
14
  PostgresUserStore,
15
15
  PostgresWorkspaceStore,
@@ -531,7 +531,8 @@ async function createCoreWorkspaceAgentServer(options = {}) {
531
531
  const { app, sql, db, userStore, workspaceStore } = await createCoreRuntime(config);
532
532
  const appRoot = options.appRoot;
533
533
  const serveFrontend = options.serveFrontend ?? (process.env.NODE_ENV !== "development" && Boolean(appRoot));
534
- const workspaceRoot = options.workspaceRoot ?? process.cwd();
534
+ const pluginWorkspaceRoot = process.cwd();
535
+ const workspaceRoot = options.workspaceRoot ?? process.env.BORING_AGENT_WORKSPACE_ROOT ?? process.cwd();
535
536
  const telemetrySource = options.telemetry ? "custom" : process.env.BORING_TELEMETRY_ENABLED === "true" ? "db-env" : "noop-env";
536
537
  const telemetry = options.telemetry ?? createDatabaseTelemetryFromEnv(db, { appId: config.appId }, process.env);
537
538
  app.log.debug({ telemetry: { source: telemetrySource } }, "resolved telemetry sink");
@@ -544,7 +545,7 @@ async function createCoreWorkspaceAgentServer(options = {}) {
544
545
  serveSpaShell: (request, reply) => serveFrontendShell(request, reply, path.resolve(appRoot, "dist/front/index.html"), telemetry)
545
546
  } : void 0);
546
547
  const defaultPluginPackagePaths = resolveDefaultWorkspacePluginPackagePaths({
547
- workspaceRoot,
548
+ workspaceRoot: pluginWorkspaceRoot,
548
549
  appPackageJsonPath: options.appPackageJsonPath,
549
550
  defaultPluginPackages: options.defaultPluginPackages
550
551
  });
@@ -568,7 +569,7 @@ async function createCoreWorkspaceAgentServer(options = {}) {
568
569
  return bridge;
569
570
  };
570
571
  const pluginResolveContext = {
571
- workspaceRoot,
572
+ workspaceRoot: pluginWorkspaceRoot,
572
573
  bridge: createUnavailableCorePluginBridge()
573
574
  };
574
575
  const resolvedPlugins = await Promise.all(
@@ -578,7 +579,7 @@ async function createCoreWorkspaceAgentServer(options = {}) {
578
579
  ))
579
580
  );
580
581
  const pluginCollection = collectWorkspaceAgentServerPlugins({
581
- workspaceRoot,
582
+ workspaceRoot: pluginWorkspaceRoot,
582
583
  systemPromptAppend: staticSystemPromptAppend,
583
584
  pi: mergePiOptions(options.pi, defaultPackagePiOptions),
584
585
  plugins: resolvedPlugins,
@@ -471,68 +471,12 @@ function useWorkspaceMembers(workspaceId) {
471
471
  }
472
472
 
473
473
  // src/front/WorkspaceAuthProvider.tsx
474
- import { createContext as createContext3, useContext as useContext3 } from "react";
474
+ import { createContext as createContext4, useContext as useContext4 } from "react";
475
475
  import { useQuery as useQuery2, useQueryClient } from "@tanstack/react-query";
476
476
  import { matchPath, useLocation, useParams } from "react-router-dom";
477
- import { jsx as jsx4 } from "react/jsx-runtime";
478
- var WorkspaceContext = createContext3({
479
- workspace: null,
480
- role: null
481
- });
482
- var WORKSPACES_QUERY_KEY = ["workspaces"];
483
- function workspaceQueryKey(workspaceId) {
484
- return ["workspace", workspaceId ?? null];
485
- }
486
- async function fetchWorkspaces() {
487
- const data = await apiFetchJson("/api/v1/workspaces");
488
- return data.workspaces;
489
- }
490
- async function fetchWorkspace(workspaceId) {
491
- return await apiFetchJson(
492
- `/api/v1/workspaces/${encodeURIComponent(workspaceId)}`
493
- );
494
- }
495
- function workspaceIdFromPath(pathname) {
496
- const match = matchPath("/w/:id/*", pathname) ?? matchPath("/w/:id", pathname) ?? matchPath("/workspace/:id/*", pathname) ?? matchPath("/workspace/:id", pathname);
497
- const id = match?.params.id?.trim();
498
- return id ? id : null;
499
- }
500
- function WorkspaceAuthProvider({ children }) {
501
- const { id } = useParams();
502
- const location = useLocation();
503
- const queryClient = useQueryClient();
504
- const routeWorkspaceId = id?.trim() ? id : workspaceIdFromPath(location.pathname);
505
- const workspacesQuery = useQuery2({
506
- queryKey: WORKSPACES_QUERY_KEY,
507
- queryFn: fetchWorkspaces
508
- });
509
- const defaultWorkspace = routeWorkspaceId === null ? workspacesQuery.data?.find((workspace2) => workspace2.isDefault) ?? workspacesQuery.data?.[0] ?? null : null;
510
- const resolvedId = routeWorkspaceId ?? defaultWorkspace?.id ?? null;
511
- const cachedDetail = resolvedId ? queryClient.getQueryData(workspaceQueryKey(resolvedId)) : void 0;
512
- const detailQuery = useQuery2({
513
- queryKey: workspaceQueryKey(resolvedId),
514
- queryFn: () => {
515
- if (!resolvedId) {
516
- throw new Error("Workspace id is required");
517
- }
518
- return fetchWorkspace(resolvedId);
519
- },
520
- enabled: resolvedId !== null
521
- });
522
- const detail = detailQuery.data ?? cachedDetail ?? null;
523
- const workspace = detailQuery.isError ? null : detail?.workspace ?? null;
524
- const role = detailQuery.isError ? null : detail?.role ?? null;
525
- return /* @__PURE__ */ jsx4(WorkspaceContext.Provider, { value: { workspace, role }, children });
526
- }
527
- function useCurrentWorkspace() {
528
- return useContext3(WorkspaceContext).workspace;
529
- }
530
- function useWorkspaceRole() {
531
- return useContext3(WorkspaceContext).role;
532
- }
533
477
 
534
478
  // src/front/auth/AuthProvider.tsx
535
- import { createContext as createContext4, useContext as useContext4, useCallback as useCallback2, useMemo as useMemo2 } from "react";
479
+ import { createContext as createContext3, useContext as useContext3, useCallback as useCallback2, useMemo as useMemo2 } from "react";
536
480
 
537
481
  // src/front/auth/authClient.ts
538
482
  import { createAuthClient } from "better-auth/react";
@@ -555,8 +499,8 @@ function getAuthClient(baseURL) {
555
499
  }
556
500
 
557
501
  // src/front/auth/AuthProvider.tsx
558
- import { jsx as jsx5 } from "react/jsx-runtime";
559
- var AuthContext = createContext4(null);
502
+ import { jsx as jsx4 } from "react/jsx-runtime";
503
+ var AuthContext = createContext3(null);
560
504
  function toISOString(value) {
561
505
  if (!value) return "";
562
506
  if (value instanceof Date) return value.toISOString();
@@ -589,10 +533,10 @@ function AuthProvider({
589
533
  () => ({ client, signOut }),
590
534
  [client, signOut]
591
535
  );
592
- return /* @__PURE__ */ jsx5(AuthContext.Provider, { value, children });
536
+ return /* @__PURE__ */ jsx4(AuthContext.Provider, { value, children });
593
537
  }
594
538
  function useAuthContext() {
595
- const ctx = useContext4(AuthContext);
539
+ const ctx = useContext3(AuthContext);
596
540
  if (!ctx) throw new Error("useSession/signIn/signOut must be used within an AuthProvider");
597
541
  return ctx;
598
542
  }
@@ -620,8 +564,21 @@ function useSignUp() {
620
564
  return client.signUp;
621
565
  }
622
566
  function useForgetPassword() {
623
- const { client } = useAuthContext();
624
- return client.forgetPassword;
567
+ useAuthContext();
568
+ return async (opts) => {
569
+ const endpoint = buildApiUrl("/auth/request-password-reset");
570
+ const response = await fetch(endpoint, {
571
+ method: "POST",
572
+ headers: { "content-type": "application/json" },
573
+ credentials: "include",
574
+ body: JSON.stringify(opts)
575
+ });
576
+ const data = await response.json().catch(() => null);
577
+ if (!response.ok) {
578
+ return { data: null, error: data ?? { status: response.status, message: "Request failed" } };
579
+ }
580
+ return { data, error: null };
581
+ };
625
582
  }
626
583
  function useResetPassword() {
627
584
  const { client } = useAuthContext();
@@ -644,6 +601,107 @@ function useSignOut() {
644
601
  return signOut;
645
602
  }
646
603
 
604
+ // src/front/WorkspaceAuthProvider.tsx
605
+ import { jsx as jsx5 } from "react/jsx-runtime";
606
+ var WorkspaceContext = createContext4({
607
+ workspace: null,
608
+ role: null,
609
+ routeStatus: { status: "idle", workspaceId: null }
610
+ });
611
+ var WORKSPACES_QUERY_KEY = ["workspaces"];
612
+ function workspaceQueryKey(workspaceId) {
613
+ return ["workspace", workspaceId ?? null];
614
+ }
615
+ async function fetchWorkspaces() {
616
+ const data = await apiFetchJson("/api/v1/workspaces");
617
+ return data.workspaces;
618
+ }
619
+ async function fetchWorkspace(workspaceId) {
620
+ return await apiFetchJson(
621
+ `/api/v1/workspaces/${encodeURIComponent(workspaceId)}`
622
+ );
623
+ }
624
+ function routePatterns(route) {
625
+ const normalized = route.endsWith("/*") ? route.slice(0, -2) : route;
626
+ return [`${normalized}/*`, normalized];
627
+ }
628
+ function workspaceIdFromPath(pathname, workspaceRoute = "/workspace/:id", workspaceIdParam = "id") {
629
+ const patterns = [
630
+ ...routePatterns(workspaceRoute),
631
+ "/w/:id/*",
632
+ "/w/:id",
633
+ "/workspace/:id/*",
634
+ "/workspace/:id"
635
+ ];
636
+ for (const pattern of patterns) {
637
+ const match = matchPath(pattern, pathname);
638
+ const id = match?.params[workspaceIdParam]?.trim() ?? match?.params.id?.trim();
639
+ if (id) return id;
640
+ }
641
+ return null;
642
+ }
643
+ function routeStatusFromError(workspaceId, error) {
644
+ const detail = getHttpErrorDetail(error);
645
+ if (detail.status === 404 || detail.code === "not_found") {
646
+ return { status: "not-found", workspaceId, message: detail.message };
647
+ }
648
+ if (detail.status === 403 || detail.code === "forbidden" || detail.code === "not_member") {
649
+ return { status: "forbidden", workspaceId, message: detail.message };
650
+ }
651
+ return { status: "switch-failed", workspaceId, message: detail.message };
652
+ }
653
+ function WorkspaceAuthProvider({
654
+ children,
655
+ workspaceRoute,
656
+ workspaceIdParam
657
+ }) {
658
+ const { id } = useParams();
659
+ const location = useLocation();
660
+ const queryClient = useQueryClient();
661
+ const routeWorkspaceId = id?.trim() ? id : workspaceIdFromPath(location.pathname, workspaceRoute, workspaceIdParam);
662
+ const session = useSession();
663
+ const isAuthenticated = Boolean(session.data?.user);
664
+ const workspacesQuery = useQuery2({
665
+ queryKey: WORKSPACES_QUERY_KEY,
666
+ queryFn: fetchWorkspaces,
667
+ enabled: isAuthenticated
668
+ });
669
+ const defaultWorkspace = routeWorkspaceId === null ? workspacesQuery.data?.find((workspace2) => workspace2.isDefault) ?? workspacesQuery.data?.[0] ?? null : null;
670
+ const resolvedId = routeWorkspaceId ?? defaultWorkspace?.id ?? null;
671
+ const cachedDetail = resolvedId ? queryClient.getQueryData(workspaceQueryKey(resolvedId)) : void 0;
672
+ const detailQuery = useQuery2({
673
+ queryKey: workspaceQueryKey(resolvedId),
674
+ queryFn: () => {
675
+ if (!resolvedId) {
676
+ throw new Error("Workspace id is required");
677
+ }
678
+ return fetchWorkspace(resolvedId);
679
+ },
680
+ enabled: isAuthenticated && resolvedId !== null
681
+ });
682
+ const detail = detailQuery.data ?? cachedDetail ?? null;
683
+ const workspace = detailQuery.isError ? null : detail?.workspace ?? null;
684
+ const role = detailQuery.isError ? null : detail?.role ?? null;
685
+ const routeStatus = (() => {
686
+ if (!isAuthenticated) return { status: "idle", workspaceId: routeWorkspaceId };
687
+ if (routeWorkspaceId === null) return { status: "idle", workspaceId: null };
688
+ if (detailQuery.isError) return routeStatusFromError(routeWorkspaceId, detailQuery.error);
689
+ if (detailQuery.isPending && !detail) return { status: "loading", workspaceId: routeWorkspaceId };
690
+ if (workspace?.id === routeWorkspaceId) return { status: "matched", workspaceId: routeWorkspaceId, workspace };
691
+ return { status: "mismatched", workspaceId: routeWorkspaceId, currentWorkspaceId: workspace?.id ?? null };
692
+ })();
693
+ return /* @__PURE__ */ jsx5(WorkspaceContext.Provider, { value: { workspace, role, routeStatus }, children });
694
+ }
695
+ function useCurrentWorkspace() {
696
+ return useContext4(WorkspaceContext).workspace;
697
+ }
698
+ function useWorkspaceRole() {
699
+ return useContext4(WorkspaceContext).role;
700
+ }
701
+ function useWorkspaceRouteStatus() {
702
+ return useContext4(WorkspaceContext).routeStatus;
703
+ }
704
+
647
705
  // src/front/auth/UserIdentityProvider.tsx
648
706
  import { createContext as createContext5, useContext as useContext5, useEffect as useEffect7, useState as useState6, useRef as useRef3 } from "react";
649
707
  import { jsx as jsx6 } from "react/jsx-runtime";
@@ -984,6 +1042,8 @@ function ForgotPasswordPage() {
984
1042
  const forgetPassword = useForgetPassword();
985
1043
  const [isSubmitting, setIsSubmitting] = useState10(false);
986
1044
  const [submitted, setSubmitted] = useState10(false);
1045
+ const redirect = typeof window === "undefined" ? null : new URLSearchParams(window.location.search).get("redirect");
1046
+ const signinHref = redirect ? `${routes.signin}?redirect=${encodeURIComponent(redirect)}` : routes.signin;
987
1047
  const {
988
1048
  register,
989
1049
  handleSubmit,
@@ -1007,7 +1067,7 @@ function ForgotPasswordPage() {
1007
1067
  /* @__PURE__ */ jsx10(CardTitle3, { children: "Check your inbox" }),
1008
1068
  /* @__PURE__ */ jsx10(CardDescription3, { children: "If an account exists with that email, we sent a password reset link. Check your inbox and follow the instructions." })
1009
1069
  ] }),
1010
- /* @__PURE__ */ jsx10(CardFooter3, { children: /* @__PURE__ */ jsx10("a", { href: routes.signin, className: "text-sm text-muted-foreground hover:underline", children: "Back to sign in" }) })
1070
+ /* @__PURE__ */ jsx10(CardFooter3, { children: /* @__PURE__ */ jsx10("a", { href: signinHref, className: "text-sm text-muted-foreground hover:underline", children: "Back to sign in" }) })
1011
1071
  ] }) });
1012
1072
  }
1013
1073
  return /* @__PURE__ */ jsx10("div", { className: "flex min-h-screen items-center justify-center p-4", children: /* @__PURE__ */ jsxs3(Card3, { className: "w-full max-w-sm", children: [
@@ -1032,7 +1092,7 @@ function ForgotPasswordPage() {
1032
1092
  ] }) }),
1033
1093
  /* @__PURE__ */ jsxs3(CardFooter3, { className: "flex flex-col gap-4", children: [
1034
1094
  /* @__PURE__ */ jsx10(Button5, { type: "submit", className: "w-full", disabled: isSubmitting, children: isSubmitting ? "Sending\u2026" : "Send reset link" }),
1035
- /* @__PURE__ */ jsx10("a", { href: routes.signin, className: "text-sm text-muted-foreground hover:underline", children: "Back to sign in" })
1095
+ /* @__PURE__ */ jsx10("a", { href: signinHref, className: "text-sm text-muted-foreground hover:underline", children: "Back to sign in" })
1036
1096
  ] })
1037
1097
  ] })
1038
1098
  ] }) });
@@ -1816,6 +1876,7 @@ function InviteAcceptPage() {
1816
1876
 
1817
1877
  // src/front/AuthGate.tsx
1818
1878
  import { useEffect as useEffect9, useMemo as useMemo3, useRef as useRef5 } from "react";
1879
+ import { matchPath as matchPath2 } from "react-router-dom";
1819
1880
  import { Fragment as Fragment3, jsx as jsx15 } from "react/jsx-runtime";
1820
1881
  var DEFAULT_GRACE_MS = 3e4;
1821
1882
  var UNSAFE_REDIRECT_RE = /[\0\r\n<>"'`]/;
@@ -1842,7 +1903,12 @@ function normalizePublicPath(path) {
1842
1903
  function isPublicPath(pathname, publicPaths) {
1843
1904
  const normalizedPath = normalizePath(pathname);
1844
1905
  if (normalizedPath === "/auth" || normalizedPath.startsWith("/auth/")) return true;
1845
- return publicPaths.some((candidate) => normalizedPath === candidate || normalizedPath.startsWith(`${candidate}/`));
1906
+ return publicPaths.some((candidate) => {
1907
+ if (candidate.includes(":") || candidate.includes("*")) {
1908
+ return Boolean(matchPath2({ path: candidate, end: !candidate.includes("*") }, normalizedPath));
1909
+ }
1910
+ return normalizedPath === candidate || normalizedPath.startsWith(`${candidate}/`);
1911
+ });
1846
1912
  }
1847
1913
  function readSafeRedirect(search) {
1848
1914
  const redirect = new URLSearchParams(normalizeSearch(search)).get("redirect");
@@ -1938,8 +2004,8 @@ function AuthGate({
1938
2004
  }
1939
2005
 
1940
2006
  // src/front/CoreFront.tsx
1941
- import { Suspense, useMemo as useMemo6 } from "react";
1942
- import { BrowserRouter, Routes, Route } from "react-router-dom";
2007
+ import { Suspense, useCallback as useCallback9, useMemo as useMemo6 } from "react";
2008
+ import { BrowserRouter, Routes, Route, useLocation as useLocation2, useNavigate as useNavigate5 } from "react-router-dom";
1943
2009
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
1944
2010
  import { Helmet, HelmetProvider } from "react-helmet-async";
1945
2011
 
@@ -2202,12 +2268,15 @@ function WorkspaceSwitcher({
2202
2268
  headers: { "content-type": "application/json" },
2203
2269
  body: JSON.stringify({ name: parsed.data.name })
2204
2270
  });
2205
- await Promise.all([
2206
- queryClient.invalidateQueries({ queryKey: WORKSPACES_QUERY_KEY }),
2207
- queryClient.invalidateQueries({ queryKey: workspaceQueryKey(data.workspace.id) })
2208
- ]);
2271
+ queryClient.setQueryData(workspaceQueryKey(data.workspace.id), data);
2272
+ queryClient.setQueryData(WORKSPACES_QUERY_KEY, (current = []) => {
2273
+ if (current.some((workspace) => workspace.id === data.workspace.id)) return current;
2274
+ return [...current, data.workspace];
2275
+ });
2209
2276
  onModalChange(false);
2210
2277
  navigate(hrefForWorkspace(workspacePathPrefix, data.workspace.id));
2278
+ void queryClient.invalidateQueries({ queryKey: WORKSPACES_QUERY_KEY });
2279
+ void queryClient.invalidateQueries({ queryKey: workspaceQueryKey(data.workspace.id) });
2211
2280
  } catch (error) {
2212
2281
  const detail = getHttpErrorDetail(error);
2213
2282
  if (typeof detail.status === "number" && detail.status >= 400 && detail.status < 500) {
@@ -3420,7 +3489,30 @@ function createDefaultQueryClient() {
3420
3489
  }
3421
3490
  });
3422
3491
  }
3423
- function CoreFront({ children, authPages, cspNonce }) {
3492
+ function RouterAuthGate({ children, publicPaths }) {
3493
+ const location = useLocation2();
3494
+ const navigate = useNavigate5();
3495
+ const authLocation = useMemo6(
3496
+ () => ({ pathname: location.pathname, search: location.search, hash: location.hash }),
3497
+ [location.hash, location.pathname, location.search]
3498
+ );
3499
+ const navigateWithinRouter = useCallback9(
3500
+ (to, options) => {
3501
+ navigate(to, { replace: options?.replace });
3502
+ },
3503
+ [navigate]
3504
+ );
3505
+ return /* @__PURE__ */ jsx22(
3506
+ AuthGate,
3507
+ {
3508
+ location: authLocation,
3509
+ navigate: navigateWithinRouter,
3510
+ publicPaths,
3511
+ children
3512
+ }
3513
+ );
3514
+ }
3515
+ function CoreFront({ children, authPages, cspNonce, workspaceRoute, workspaceIdParam, publicPaths }) {
3424
3516
  const queryClient = useMemo6(createDefaultQueryClient, []);
3425
3517
  const resolvedCspNonce = useMemo6(
3426
3518
  () => cspNonce ?? readCspNonceFromDom(),
@@ -3433,7 +3525,7 @@ function CoreFront({ children, authPages, cspNonce }) {
3433
3525
  const VerifyEmailPage2 = authPages?.verifyEmail ?? VerifyEmailPage;
3434
3526
  const AuthErrorPage2 = authPages?.authError ?? AuthErrorPage;
3435
3527
  const UserSettingsPage2 = authPages?.userSettings ?? UserSettingsPage;
3436
- return /* @__PURE__ */ jsx22(HelmetProvider, { children: /* @__PURE__ */ jsx22(AppErrorBoundary, { children: /* @__PURE__ */ jsx22(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx22(ConfigProvider, { children: /* @__PURE__ */ jsx22(ThemeProvider, { children: /* @__PURE__ */ jsx22(AuthProvider, { queryClient, children: /* @__PURE__ */ jsx22(UserIdentityProvider, { children: /* @__PURE__ */ jsx22(BrowserRouter, { children: /* @__PURE__ */ jsx22(WorkspaceAuthProvider, { children: /* @__PURE__ */ jsxs15(TopBarSlotProvider, { slot: /* @__PURE__ */ jsx22(UserMenu, {}), children: [
3528
+ return /* @__PURE__ */ jsx22(HelmetProvider, { children: /* @__PURE__ */ jsx22(AppErrorBoundary, { children: /* @__PURE__ */ jsx22(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx22(ConfigProvider, { children: /* @__PURE__ */ jsx22(ThemeProvider, { children: /* @__PURE__ */ jsx22(AuthProvider, { queryClient, children: /* @__PURE__ */ jsx22(UserIdentityProvider, { children: /* @__PURE__ */ jsx22(BrowserRouter, { children: /* @__PURE__ */ jsx22(WorkspaceAuthProvider, { workspaceRoute, workspaceIdParam, children: /* @__PURE__ */ jsxs15(TopBarSlotProvider, { slot: /* @__PURE__ */ jsx22(UserMenu, {}), children: [
3437
3529
  /* @__PURE__ */ jsx22(Helmet, { children: resolvedCspNonce ? /* @__PURE__ */ jsxs15(Fragment6, { children: [
3438
3530
  /* @__PURE__ */ jsx22("meta", { name: CSP_NONCE_META_NAME, content: resolvedCspNonce }),
3439
3531
  /* @__PURE__ */ jsx22(
@@ -3446,7 +3538,7 @@ function CoreFront({ children, authPages, cspNonce }) {
3446
3538
  }
3447
3539
  )
3448
3540
  ] }) : null }),
3449
- /* @__PURE__ */ jsx22(AuthGate, { publicPaths: ["/invites"], children: /* @__PURE__ */ jsx22(Suspense, { fallback: null, children: /* @__PURE__ */ jsxs15(Routes, { children: [
3541
+ /* @__PURE__ */ jsx22(RouterAuthGate, { publicPaths: ["/invites", ...publicPaths ?? []], children: /* @__PURE__ */ jsx22(Suspense, { fallback: null, children: /* @__PURE__ */ jsxs15(Routes, { children: [
3450
3542
  /* @__PURE__ */ jsx22(Route, { path: routes.signin, element: /* @__PURE__ */ jsx22(SignInPage2, {}) }),
3451
3543
  /* @__PURE__ */ jsx22(Route, { path: routes.signup, element: /* @__PURE__ */ jsx22(SignUpPage2, {}) }),
3452
3544
  /* @__PURE__ */ jsx22(Route, { path: routes.forgotPassword, element: /* @__PURE__ */ jsx22(ForgotPasswordPage2, {}) }),
@@ -3470,7 +3562,7 @@ function CoreFront({ children, authPages, cspNonce }) {
3470
3562
 
3471
3563
  // src/front/commands/CoreCommandContributions.tsx
3472
3564
  import { useMemo as useMemo7, useState as useState20 } from "react";
3473
- import { useNavigate as useNavigate5 } from "react-router-dom";
3565
+ import { useNavigate as useNavigate6 } from "react-router-dom";
3474
3566
 
3475
3567
  // src/front/workspace/commands.ts
3476
3568
  function getWorkspaceCommands(workspaceId, navigate) {
@@ -3508,7 +3600,7 @@ function toPaletteCommand(command) {
3508
3600
  };
3509
3601
  }
3510
3602
  function useCoreCommands() {
3511
- const navigate = useNavigate5();
3603
+ const navigate = useNavigate6();
3512
3604
  const signOut = useSignOut();
3513
3605
  const workspace = useCurrentWorkspace();
3514
3606
  const [isSigningOut, setIsSigningOut] = useState20(false);
@@ -3632,9 +3724,6 @@ export {
3632
3724
  useBlobUrl,
3633
3725
  useCapabilities,
3634
3726
  useWorkspaceMembers,
3635
- WorkspaceAuthProvider,
3636
- useCurrentWorkspace,
3637
- useWorkspaceRole,
3638
3727
  getAuthClient,
3639
3728
  AuthProvider,
3640
3729
  useSession,
@@ -3644,6 +3733,10 @@ export {
3644
3733
  useSendVerificationEmail,
3645
3734
  useChangePassword,
3646
3735
  useSignOut,
3736
+ WorkspaceAuthProvider,
3737
+ useCurrentWorkspace,
3738
+ useWorkspaceRole,
3739
+ useWorkspaceRouteStatus,
3647
3740
  UserIdentityProvider,
3648
3741
  useUser,
3649
3742
  GoogleAuthButton,
@@ -1951,9 +1951,60 @@ var updateWorkspaceBody = z3.object({
1951
1951
  }).strict();
1952
1952
 
1953
1953
  // src/server/routes/workspaces.ts
1954
+ var DEFAULT_WORKSPACE_NAME = "My Workspace";
1954
1955
  var workspaceRoutesPlugin = async (app) => {
1955
1956
  const store = app.workspaceStore;
1956
1957
  const provisioner = app.provisioner;
1958
+ async function provisionWorkspace(workspace, ownerId, request) {
1959
+ if (!provisioner) return;
1960
+ await store.putWorkspaceRuntime(workspace.id, { state: "pending" });
1961
+ try {
1962
+ const result = await provisioner.provision({
1963
+ workspaceId: workspace.id,
1964
+ workspaceName: workspace.name,
1965
+ ownerId,
1966
+ appId: app.config.appId
1967
+ });
1968
+ await store.putWorkspaceRuntime(workspace.id, {
1969
+ state: "ready",
1970
+ volumePath: result.volumePath
1971
+ });
1972
+ } catch (err) {
1973
+ const message = err instanceof Error ? err.message : String(err);
1974
+ await store.putWorkspaceRuntime(workspace.id, {
1975
+ state: "error",
1976
+ lastError: message,
1977
+ lastErrorOp: "provision"
1978
+ });
1979
+ request.log.error({ workspaceId: workspace.id, err }, "workspace.provision.failed");
1980
+ throw new HttpError({
1981
+ status: 500,
1982
+ code: ERROR_CODES.PROVISION_FAILED,
1983
+ message: "Workspace provisioning failed",
1984
+ requestId: request.id
1985
+ });
1986
+ }
1987
+ }
1988
+ async function createWorkspaceForUser(userId, name, isDefault, request) {
1989
+ const workspace = await store.create(userId, name, app.config.appId, { isDefault });
1990
+ await provisionWorkspace(workspace, userId, request);
1991
+ return workspace;
1992
+ }
1993
+ async function ensureDefaultWorkspaceProvisioned(workspace, request) {
1994
+ if (!provisioner || !workspace.isDefault) return;
1995
+ const runtime = await store.getWorkspaceRuntime(workspace.id);
1996
+ const needsProvisioning = !runtime || runtime.state === "ready" && !runtime.volumePath;
1997
+ if (needsProvisioning) await provisionWorkspace(workspace, workspace.createdBy, request);
1998
+ }
1999
+ async function listOrCreateDefaultWorkspace(userId, request) {
2000
+ const existing = await store.list(userId, app.config.appId);
2001
+ if (existing.length > 0) {
2002
+ await Promise.all(existing.map((workspace) => ensureDefaultWorkspaceProvisioned(workspace, request)));
2003
+ return existing;
2004
+ }
2005
+ const created = await createWorkspaceForUser(userId, DEFAULT_WORKSPACE_NAME, true, request);
2006
+ return [created];
2007
+ }
1957
2008
  app.post("/api/v1/workspaces", async (request, reply) => {
1958
2009
  const parsed = createWorkspaceBody.safeParse(request.body);
1959
2010
  if (!parsed.success) {
@@ -1967,42 +2018,13 @@ var workspaceRoutesPlugin = async (app) => {
1967
2018
  const user = request.user;
1968
2019
  const existing = await store.list(user.id, app.config.appId);
1969
2020
  const isDefault = existing.length === 0;
1970
- const workspace = await store.create(user.id, parsed.data.name, app.config.appId, { isDefault });
1971
- if (provisioner) {
1972
- await store.putWorkspaceRuntime(workspace.id, { state: "pending" });
1973
- try {
1974
- const result = await provisioner.provision({
1975
- workspaceId: workspace.id,
1976
- workspaceName: workspace.name,
1977
- ownerId: user.id,
1978
- appId: app.config.appId
1979
- });
1980
- await store.putWorkspaceRuntime(workspace.id, {
1981
- state: "ready",
1982
- volumePath: result.volumePath
1983
- });
1984
- } catch (err) {
1985
- const message = err instanceof Error ? err.message : String(err);
1986
- await store.putWorkspaceRuntime(workspace.id, {
1987
- state: "error",
1988
- lastError: message,
1989
- lastErrorOp: "provision"
1990
- });
1991
- request.log.error({ workspaceId: workspace.id, err }, "workspace.provision.failed");
1992
- throw new HttpError({
1993
- status: 500,
1994
- code: ERROR_CODES.PROVISION_FAILED,
1995
- message: "Workspace provisioning failed",
1996
- requestId: request.id
1997
- });
1998
- }
1999
- }
2021
+ const workspace = await createWorkspaceForUser(user.id, parsed.data.name, isDefault, request);
2000
2022
  request.log.info({ workspaceId: workspace.id, userId: user.id }, "workspace.create");
2001
2023
  reply.status(201);
2002
2024
  return { workspace, role: "owner" };
2003
2025
  });
2004
2026
  app.get("/api/v1/workspaces", async (request) => {
2005
- const workspaces2 = await store.list(request.user.id, app.config.appId);
2027
+ const workspaces2 = await listOrCreateDefaultWorkspace(request.user.id, request);
2006
2028
  return { workspaces: workspaces2 };
2007
2029
  });
2008
2030
  app.get(
@@ -6,7 +6,7 @@ import * as _tanstack_react_query from '@tanstack/react-query';
6
6
  import * as better_auth_react from 'better-auth/react';
7
7
  import { createAuthClient } from 'better-auth/react';
8
8
  import * as better_auth from 'better-auth';
9
- export { a as CoreFront, C as CoreFrontAuthPagesOverride, b as CoreFrontProps } from '../CoreFront-CgAkiEts.js';
9
+ export { a as CoreFront, C as CoreFrontAuthPagesOverride, b as CoreFrontProps } from '../CoreFront-N0QJSYaM.js';
10
10
  import { NavigateFunction } from 'react-router-dom';
11
11
  export { TopBarSlotProvider, useTopBarSlot } from './top-bar-slot.js';
12
12
 
@@ -70,12 +70,42 @@ type EnrichedMember = WorkspaceMember & {
70
70
  };
71
71
  declare function useWorkspaceMembers(workspaceId: string): _tanstack_react_query.UseQueryResult<EnrichedMember[], Error>;
72
72
 
73
+ type WorkspaceRouteStatus = {
74
+ status: 'idle';
75
+ workspaceId: string | null;
76
+ } | {
77
+ status: 'loading';
78
+ workspaceId: string | null;
79
+ } | {
80
+ status: 'matched';
81
+ workspaceId: string;
82
+ workspace: Workspace;
83
+ } | {
84
+ status: 'mismatched';
85
+ workspaceId: string;
86
+ currentWorkspaceId: string | null;
87
+ } | {
88
+ status: 'not-found';
89
+ workspaceId: string;
90
+ message: string;
91
+ } | {
92
+ status: 'forbidden';
93
+ workspaceId: string;
94
+ message: string;
95
+ } | {
96
+ status: 'switch-failed';
97
+ workspaceId: string;
98
+ message: string;
99
+ };
73
100
  interface WorkspaceAuthProviderProps {
74
101
  children: ReactNode;
102
+ workspaceRoute?: string;
103
+ workspaceIdParam?: string;
75
104
  }
76
- declare function WorkspaceAuthProvider({ children }: WorkspaceAuthProviderProps): react_jsx_runtime.JSX.Element;
105
+ declare function WorkspaceAuthProvider({ children, workspaceRoute, workspaceIdParam, }: WorkspaceAuthProviderProps): react_jsx_runtime.JSX.Element;
77
106
  declare function useCurrentWorkspace(): Workspace | null;
78
107
  declare function useWorkspaceRole(): MemberRole | null;
108
+ declare function useWorkspaceRouteStatus(): WorkspaceRouteStatus;
79
109
 
80
110
  interface AuthProviderProps {
81
111
  children: ReactNode;
@@ -464,4 +494,4 @@ declare function sanitizeToolOutput(input: string): string;
464
494
 
465
495
  declare function debounce<T extends (...args: unknown[]) => unknown>(fn: T, ms: number): T;
466
496
 
467
- export { AppErrorBoundary, type AuthClient, AuthGate, type AuthGateProps, AuthProvider, type AuthProviderProps, type Binding, type Breakpoint, ConfigProvider, type ConfigProviderProps, type CoreCommand, type EnrichedMember, ForgotPasswordPage, GoogleAuthButton, type GoogleAuthButtonProps, InviteAcceptPage, InvitesPage, MembersPage, ResetPasswordPage, type RouteMap, SignInPage, SignUpPage, type ThemeApi, ThemeProvider, type ThemeProviderProps, ThemeToggle, type UserIdentity, UserIdentityProvider, type UserIdentityProviderProps, UserMenu, UserSettingsPage, VerifyEmailPage, WorkspaceAuthProvider, type WorkspaceAuthProviderProps, type WorkspaceCommand, WorkspaceSettingsPage, WorkspaceSwitcher, apiFetch, apiFetchJson, buildApiUrl, buildWsUrl, debounce, getApiBase, getAuthClient, getHttpErrorDetail, getWorkspaceCommands, getWsBase, openWebSocket, routeHref, routes, sanitizeMarkdown, sanitizeToolOutput, setApiBase, useBlobUrl, useCapabilities, useChangePassword, useConfig, useConfigLoaded, useCoreCommands, useCurrentWorkspace, useKeyboardShortcuts, useReducedMotion, useSendVerificationEmail, useSession, useSignIn, useSignOut, useSignUp, useTheme, useUser, useVerifyEmail, useViewportBreakpoint, useWorkspaceMembers, useWorkspaceRole };
497
+ export { AppErrorBoundary, type AuthClient, AuthGate, type AuthGateProps, AuthProvider, type AuthProviderProps, type Binding, type Breakpoint, ConfigProvider, type ConfigProviderProps, type CoreCommand, type EnrichedMember, ForgotPasswordPage, GoogleAuthButton, type GoogleAuthButtonProps, InviteAcceptPage, InvitesPage, MembersPage, ResetPasswordPage, type RouteMap, SignInPage, SignUpPage, type ThemeApi, ThemeProvider, type ThemeProviderProps, ThemeToggle, type UserIdentity, UserIdentityProvider, type UserIdentityProviderProps, UserMenu, UserSettingsPage, VerifyEmailPage, WorkspaceAuthProvider, type WorkspaceAuthProviderProps, type WorkspaceCommand, type WorkspaceRouteStatus, WorkspaceSettingsPage, WorkspaceSwitcher, apiFetch, apiFetchJson, buildApiUrl, buildWsUrl, debounce, getApiBase, getAuthClient, getHttpErrorDetail, getWorkspaceCommands, getWsBase, openWebSocket, routeHref, routes, sanitizeMarkdown, sanitizeToolOutput, setApiBase, useBlobUrl, useCapabilities, useChangePassword, useConfig, useConfigLoaded, useCoreCommands, useCurrentWorkspace, useKeyboardShortcuts, useReducedMotion, useSendVerificationEmail, useSession, useSignIn, useSignOut, useSignUp, useTheme, useUser, useVerifyEmail, useViewportBreakpoint, useWorkspaceMembers, useWorkspaceRole, useWorkspaceRouteStatus };
@@ -56,8 +56,9 @@ import {
56
56
  useVerifyEmail,
57
57
  useViewportBreakpoint,
58
58
  useWorkspaceMembers,
59
- useWorkspaceRole
60
- } from "../chunk-JMCBLJ6W.js";
59
+ useWorkspaceRole,
60
+ useWorkspaceRouteStatus
61
+ } from "../chunk-5R3U6QKD.js";
61
62
  import {
62
63
  TopBarSlotProvider,
63
64
  useTopBarSlot
@@ -124,5 +125,6 @@ export {
124
125
  useVerifyEmail,
125
126
  useViewportBreakpoint,
126
127
  useWorkspaceMembers,
127
- useWorkspaceRole
128
+ useWorkspaceRole,
129
+ useWorkspaceRouteStatus
128
130
  };
@@ -24,7 +24,7 @@ import {
24
24
  requireWorkspaceMember,
25
25
  validateConfig,
26
26
  validatePasswordStrength
27
- } from "../chunk-6D7LEQSL.js";
27
+ } from "../chunk-TAXVSQDW.js";
28
28
  import {
29
29
  createDatabase,
30
30
  runMigrations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-core",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
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.",
@@ -73,14 +73,15 @@
73
73
  "nodemailer": "^8.0.6",
74
74
  "pino": "^10.3.1",
75
75
  "postgres": "^3.4.9",
76
+ "posthog-node": "^5.35.1",
76
77
  "react-helmet-async": "^2.0.5",
77
78
  "react-hook-form": "^7.74.0",
78
79
  "react-router-dom": "^7.14.2",
79
80
  "smol-toml": "^1.6.1",
80
81
  "zod": "^3.25.76",
81
- "@hachej/boring-agent": "0.1.22",
82
- "@hachej/boring-ui-kit": "0.1.22",
83
- "@hachej/boring-workspace": "0.1.22"
82
+ "@hachej/boring-agent": "0.1.24",
83
+ "@hachej/boring-workspace": "0.1.24",
84
+ "@hachej/boring-ui-kit": "0.1.24"
84
85
  },
85
86
  "devDependencies": {
86
87
  "@testing-library/jest-dom": "^6.9.1",
@@ -112,11 +113,11 @@
112
113
  }
113
114
  },
114
115
  "scripts": {
115
- "build": "pnpm --filter @hachej/boring-ui-kit build && tsup && cp src/front/theme.css dist/front/theme.css && mkdir -p dist/app/front && cp src/app/front/styles.css dist/app/front/styles.css",
116
+ "build": "tsup && cp src/front/theme.css dist/front/theme.css && mkdir -p dist/app/front && cp src/app/front/styles.css dist/app/front/styles.css",
116
117
  "dev": "tsup --watch",
117
118
  "test": "vitest run --no-file-parallelism",
118
119
  "test:watch": "vitest",
119
- "typecheck": "pnpm --filter @hachej/boring-ui-kit build && tsc --noEmit",
120
+ "typecheck": "tsc --noEmit",
120
121
  "lint": "pnpm run typecheck",
121
122
  "check:bundle-size": "tsx ./scripts/check-bundle-size.ts",
122
123
  "clean": "rm -rf dist .tsbuildinfo",