@hachej/boring-core 0.1.23 → 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.
- package/dist/{CoreFront-CgAkiEts.d.ts → CoreFront-N0QJSYaM.d.ts} +4 -1
- package/dist/app/front/index.d.ts +4 -2
- package/dist/app/front/index.js +451 -70
- package/dist/app/server/index.js +6 -5
- package/dist/{chunk-JMCBLJ6W.js → chunk-5R3U6QKD.js} +174 -81
- package/dist/{chunk-6D7LEQSL.js → chunk-TAXVSQDW.js} +53 -31
- package/dist/front/index.d.ts +33 -3
- package/dist/front/index.js +5 -3
- package/dist/server/index.js +1 -1
- package/package.json +7 -6
|
@@ -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-
|
|
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 };
|
package/dist/app/front/index.js
CHANGED
|
@@ -2,69 +2,408 @@ import {
|
|
|
2
2
|
CoreFront,
|
|
3
3
|
UserMenu,
|
|
4
4
|
WorkspaceSwitcher,
|
|
5
|
-
|
|
6
|
-
|
|
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__ */
|
|
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__ */
|
|
30
|
-
/* @__PURE__ */
|
|
31
|
-
/* @__PURE__ */
|
|
32
|
-
/* @__PURE__ */
|
|
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__ */
|
|
38
|
-
/* @__PURE__ */
|
|
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__ */
|
|
46
|
-
/* @__PURE__ */
|
|
47
|
-
/* @__PURE__ */
|
|
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
|
-
|
|
57
|
-
|
|
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
|
|
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__ */
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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__ */
|
|
110
|
-
topBarRight = /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
166
|
-
|
|
543
|
+
),
|
|
544
|
+
children
|
|
545
|
+
]
|
|
546
|
+
}
|
|
547
|
+
);
|
|
167
548
|
}
|
|
168
549
|
export {
|
|
169
550
|
CoreWorkspaceAgentFront
|
package/dist/app/server/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
registerRoutes,
|
|
10
10
|
registerSettingsRoutes,
|
|
11
11
|
registerWorkspaceRoutes
|
|
12
|
-
} from "../../chunk-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
559
|
-
var AuthContext =
|
|
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__ */
|
|
536
|
+
return /* @__PURE__ */ jsx4(AuthContext.Provider, { value, children });
|
|
593
537
|
}
|
|
594
538
|
function useAuthContext() {
|
|
595
|
-
const ctx =
|
|
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
|
-
|
|
624
|
-
return
|
|
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:
|
|
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:
|
|
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) =>
|
|
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
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
2027
|
+
const workspaces2 = await listOrCreateDefaultWorkspace(request.user.id, request);
|
|
2006
2028
|
return { workspaces: workspaces2 };
|
|
2007
2029
|
});
|
|
2008
2030
|
app.get(
|
package/dist/front/index.d.ts
CHANGED
|
@@ -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-
|
|
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 };
|
package/dist/front/index.js
CHANGED
|
@@ -56,8 +56,9 @@ import {
|
|
|
56
56
|
useVerifyEmail,
|
|
57
57
|
useViewportBreakpoint,
|
|
58
58
|
useWorkspaceMembers,
|
|
59
|
-
useWorkspaceRole
|
|
60
|
-
|
|
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
|
};
|
package/dist/server/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-core",
|
|
3
|
-
"version": "0.1.
|
|
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.
|
|
82
|
-
"@hachej/boring-workspace": "0.1.
|
|
83
|
-
"@hachej/boring-ui-kit": "0.1.
|
|
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": "
|
|
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": "
|
|
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",
|