@hachej/boring-core 0.1.23 → 0.1.26

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,25 @@
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
+ import { ChatSuggestion } from '@hachej/boring-agent/front';
5
6
 
7
+ interface ChatFirstPublicShellOptions {
8
+ composerPlaceholder?: string;
9
+ emptyState?: {
10
+ eyebrow?: string;
11
+ title?: string;
12
+ description?: string;
13
+ };
14
+ suggestions?: ChatSuggestion[];
15
+ }
16
+
17
+ type ChatEntryMode = 'auth-first' | 'chat-first';
6
18
  interface CoreWorkspaceAgentFrontProps<TSession extends WorkspaceAgentSession = WorkspaceAgentSession> extends Omit<WorkspaceAgentFrontProps<TSession>, 'workspaceId' | 'frontPluginHotReload' | 'hotReloadEnabled'> {
7
19
  /** Core consumes plugins statically for now; app-level hot reload is explicitly unsupported. */
8
20
  hotReload?: false;
21
+ chatEntryMode?: ChatEntryMode;
22
+ chatFirstPublicShell?: ChatFirstPublicShellOptions;
9
23
  authPages?: CoreFrontAuthPagesOverride;
10
24
  cspNonce?: string;
11
25
  children?: ReactNode;
@@ -15,6 +29,6 @@ interface CoreWorkspaceAgentFrontProps<TSession extends WorkspaceAgentSession =
15
29
  loadingFallback?: ReactNode;
16
30
  bootPreloadPaths?: string[];
17
31
  }
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;
32
+ declare function CoreWorkspaceAgentFront<TSession extends WorkspaceAgentSession = WorkspaceAgentSession>({ authPages, cspNonce, children, workspaceRoute, workspaceIdParam, workspaceHref, loadingFallback, bootPreloadPaths, topBarLeft, topBarRight, appTitle, bridgeEndpoint, hotReload, chatEntryMode, chatFirstPublicShell, ...workspaceProps }: CoreWorkspaceAgentFrontProps<TSession>): react_jsx_runtime.JSX.Element;
19
33
 
20
- export { CoreWorkspaceAgentFront, type CoreWorkspaceAgentFrontProps };
34
+ export { type ChatFirstPublicShellOptions, CoreWorkspaceAgentFront, type CoreWorkspaceAgentFrontProps };
@@ -2,69 +2,416 @@ 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
+ var defaultPublicEmptyState = {
238
+ eyebrow: "Start here",
239
+ title: "What do you want to build?",
240
+ description: "Type a prompt or pick an example. Sign in on send to unlock your private workspace."
241
+ };
242
+ var defaultPublicSuggestions = [
243
+ { 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." },
244
+ { label: "Understand a codebase", hint: "Maps the repo and explains where to start", prompt: "Explain this codebase, map the architecture, and suggest first improvements." },
245
+ { 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." },
246
+ { label: "Prototype an interface", hint: "Turns an idea into an interactive UI", prompt: "Build an interactive prototype and open it in the workspace." }
247
+ ];
248
+ function readComposerDraftFromDom() {
249
+ if (typeof document === "undefined") return "";
250
+ const input = document.querySelector('[data-boring-agent-part="composer-input"]');
251
+ return input?.value ?? "";
252
+ }
253
+ function ChatFirstPublicShell({
254
+ appTitle,
255
+ intendedWorkspaceId,
256
+ publicShell,
257
+ workspaceProps
258
+ }) {
259
+ const location = useLocation();
260
+ const [modalOpen, setModalOpen] = useState2(false);
261
+ const returnTo = safeReturnTo(location.pathname, location.search, location.hash);
262
+ const workspaceId = intendedWorkspaceId || "public";
263
+ const openAuth = (draft = readComposerDraftFromDom()) => {
264
+ writePendingChatEntry({ draft, returnTo, ...intendedWorkspaceId ? { intendedWorkspaceId } : {} });
265
+ setModalOpen(true);
266
+ };
267
+ return /* @__PURE__ */ jsxs3("div", { className: "relative h-screen min-h-0", children: [
268
+ /* @__PURE__ */ jsx4(
269
+ ChatFirstAuthenticatedShell,
270
+ {
271
+ appTitle,
272
+ workspaceId,
273
+ showComposerBlocker: false,
274
+ workspaceProps: {
275
+ ...workspaceProps,
276
+ 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" }),
277
+ className: workspaceProps.className,
278
+ surfaceButtonBottomOffset: 456,
279
+ chatParams: {
280
+ ...workspaceProps.chatParams,
281
+ emptyPlacement: "hero",
282
+ composerPlaceholder: publicShell?.composerPlaceholder ?? "Describe the app, bug, or repo task you want help with\u2026",
283
+ emptyState: {
284
+ ...defaultPublicEmptyState,
285
+ ...publicShell?.emptyState
286
+ },
287
+ suggestions: publicShell?.suggestions ?? defaultPublicSuggestions,
288
+ onBeforeSubmit: (draft) => {
289
+ openAuth(draft);
290
+ return false;
291
+ }
292
+ }
293
+ }
294
+ }
295
+ ),
296
+ /* @__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 }) }) }),
297
+ modalOpen ? /* @__PURE__ */ jsx4(AuthModal, { returnTo, onClose: () => setModalOpen(false) }) : null
298
+ ] });
299
+ }
300
+
301
+ // src/app/front/CoreWorkspaceAgentFront.tsx
302
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
19
303
  var DEFAULT_WORKSPACE_ROUTE = "/workspace/:id";
20
304
  var DEFAULT_WORKSPACE_ID_PARAM = "id";
21
305
  function DefaultTopBarRight() {
22
- return /* @__PURE__ */ jsx(UserMenu, {});
306
+ return /* @__PURE__ */ jsx5(UserMenu, {});
23
307
  }
24
308
  function WorkspaceLoadingPage({
25
309
  appTitle,
26
310
  topBarLeft,
27
311
  topBarRight
28
312
  }) {
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 }),
313
+ return /* @__PURE__ */ jsxs4("div", { className: "flex h-screen min-h-0 flex-col bg-background text-foreground", children: [
314
+ /* @__PURE__ */ jsxs4("header", { className: "flex h-12 shrink-0 items-center justify-between border-b border-border bg-card px-3", children: [
315
+ /* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 items-center gap-3", children: [
316
+ /* @__PURE__ */ jsx5("div", { className: "text-sm font-semibold", children: appTitle }),
33
317
  topBarLeft
34
318
  ] }),
35
319
  topBarRight
36
320
  ] }),
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(
321
+ /* @__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: [
322
+ /* @__PURE__ */ jsx5(
39
323
  "div",
40
324
  {
41
325
  "aria-hidden": "true",
42
326
  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
327
  }
44
328
  ),
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" })
329
+ /* @__PURE__ */ jsx5("h1", { className: "text-lg font-semibold", children: "Switching workspace" }),
330
+ /* @__PURE__ */ jsx5("p", { className: "mt-2 text-sm text-muted-foreground", children: "Restoring files, sessions, and saved layout." }),
331
+ /* @__PURE__ */ jsx5("p", { className: "mt-4 text-xs uppercase tracking-[0.18em] text-muted-foreground", children: "Loading workspace" })
48
332
  ] }) })
49
333
  ] });
50
334
  }
335
+ function usePendingChatDraft() {
336
+ const session = useSession();
337
+ const userId = session.data?.user?.id ?? null;
338
+ const [pending, setPending] = useState3(() => userId ? readPendingChatEntry() : null);
339
+ useEffect2(() => {
340
+ if (!userId) {
341
+ setPending(null);
342
+ return;
343
+ }
344
+ setPending(readPendingChatEntry());
345
+ const syncPending = () => setPending(readPendingChatEntry());
346
+ globalThis.addEventListener?.(PENDING_CHAT_ENTRY_CHANGED_EVENT, syncPending);
347
+ return () => globalThis.removeEventListener?.(PENDING_CHAT_ENTRY_CHANGED_EVENT, syncPending);
348
+ }, [userId]);
349
+ return pending;
350
+ }
51
351
  function HomeRedirect({
52
352
  loadingFallback,
53
- workspaceHref
353
+ workspaceHref,
354
+ chatEntryMode,
355
+ appTitle,
356
+ workspaceProps,
357
+ chatFirstPublicShell
54
358
  }) {
359
+ const location = useLocation2();
360
+ const session = useSession();
55
361
  const workspace = useCurrentWorkspace();
56
- if (!workspace) return /* @__PURE__ */ jsx(Fragment, { children: loadingFallback });
57
- return /* @__PURE__ */ jsx(Navigate, { to: workspaceHref(workspace.id), replace: true });
362
+ const pendingChatEntry = usePendingChatDraft();
363
+ const restorePendingDraft = pendingChatEntryMatchesLocation(
364
+ pendingChatEntry,
365
+ location.pathname,
366
+ location.search,
367
+ location.hash
368
+ );
369
+ if (!session.data?.user && chatEntryMode === "chat-first") return /* @__PURE__ */ jsx5(ChatFirstPublicShell, { appTitle, publicShell: chatFirstPublicShell, workspaceProps });
370
+ if (!workspace && chatEntryMode === "chat-first" && session.data?.user && restorePendingDraft) {
371
+ return /* @__PURE__ */ jsx5(
372
+ ChatFirstAuthenticatedShell,
373
+ {
374
+ appTitle,
375
+ workspaceId: pendingChatEntry?.intendedWorkspaceId ?? DEFAULT_CHAT_FIRST_PENDING_WORKSPACE_ID,
376
+ initialDraft: pendingChatEntry?.draft,
377
+ workspaceProps
378
+ }
379
+ );
380
+ }
381
+ if (!workspace) return /* @__PURE__ */ jsx5(Fragment2, { children: loadingFallback });
382
+ return /* @__PURE__ */ jsx5(Navigate, { to: workspaceHref(workspace.id), replace: true });
383
+ }
384
+ function WorkspaceRouteErrorPage({ status, message }) {
385
+ const title = status === "not-found" ? "Workspace not found" : status === "forbidden" ? "Workspace unavailable" : "Workspace failed to open";
386
+ 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: [
387
+ /* @__PURE__ */ jsx5("h1", { className: "text-lg font-semibold", children: title }),
388
+ /* @__PURE__ */ jsx5("p", { className: "mt-2 text-sm text-muted-foreground", children: message })
389
+ ] }) });
58
390
  }
59
391
  function WorkspaceRoute({
60
392
  workspaceIdParam,
61
393
  loadingFallback,
62
394
  bootPreloadPaths,
63
- workspaceProps
395
+ workspaceProps,
396
+ chatEntryMode,
397
+ appTitle,
398
+ workspaceRoute,
399
+ chatFirstPublicShell
64
400
  }) {
65
401
  const params = useParams();
402
+ const location = useLocation2();
403
+ const session = useSession();
404
+ const pendingChatEntry = usePendingChatDraft();
66
405
  const currentWorkspace = useCurrentWorkspace();
67
- const workspaceId = params[workspaceIdParam]?.trim() ?? "";
406
+ const routeStatus = useWorkspaceRouteStatus();
407
+ const workspaceId = params[workspaceIdParam]?.trim() ?? workspaceIdFromPath(location.pathname, workspaceRoute, workspaceIdParam) ?? "";
408
+ const pendingDraftTargetsWorkspace = !pendingChatEntry?.intendedWorkspaceId || pendingChatEntry.intendedWorkspaceId === workspaceId;
409
+ const restorePendingDraft = pendingDraftTargetsWorkspace && (pendingChatEntryMatchesLocation(
410
+ pendingChatEntry,
411
+ location.pathname,
412
+ location.search,
413
+ location.hash
414
+ ) || pendingChatEntry?.returnTo === "/" && currentWorkspace?.id === workspaceId);
68
415
  const requestHeaders = useMemo(
69
416
  () => ({ ...workspaceProps.requestHeaders, "x-boring-workspace-id": workspaceId }),
70
417
  [workspaceId, workspaceProps.requestHeaders]
@@ -73,30 +420,55 @@ function WorkspaceRoute({
73
420
  () => ({ ...workspaceProps.authHeaders, "x-boring-workspace-id": workspaceId }),
74
421
  [workspaceId, workspaceProps.authHeaders]
75
422
  );
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,
423
+ if (!workspaceId) return /* @__PURE__ */ jsx5(Fragment2, { children: loadingFallback });
424
+ if (!session.data?.user && chatEntryMode === "chat-first") {
425
+ return /* @__PURE__ */ jsx5(ChatFirstPublicShell, { appTitle, intendedWorkspaceId: workspaceId, publicShell: chatFirstPublicShell, workspaceProps });
426
+ }
427
+ if (routeStatus.status === "not-found" || routeStatus.status === "forbidden" || routeStatus.status === "switch-failed") {
428
+ return /* @__PURE__ */ jsx5(WorkspaceRouteErrorPage, { status: routeStatus.status, message: routeStatus.message });
429
+ }
430
+ if (chatEntryMode === "chat-first" && restorePendingDraft && (routeStatus.status !== "matched" || currentWorkspace?.id !== workspaceId)) {
431
+ return /* @__PURE__ */ jsx5(
432
+ ChatFirstAuthenticatedShell,
433
+ {
434
+ appTitle,
435
+ workspaceId,
436
+ initialDraft: pendingChatEntry?.draft,
437
+ workspaceProps
438
+ }
439
+ );
440
+ }
441
+ if (routeStatus.status !== "matched" || currentWorkspace?.id !== workspaceId) return /* @__PURE__ */ jsx5(Fragment2, { children: loadingFallback });
442
+ const shouldRestorePendingDraft = restorePendingDraft && Boolean(pendingChatEntry?.draft);
443
+ const chatParams = {
444
+ ...workspaceProps.chatParams,
445
+ ...shouldRestorePendingDraft ? { initialDraft: pendingChatEntry?.draft } : {},
446
+ ...shouldRestorePendingDraft ? { autoSubmitInitialDraft: true } : {},
447
+ onBeforeSubmit: async (draft, ctx) => {
448
+ const existing = workspaceProps.chatParams?.onBeforeSubmit;
449
+ const result = await existing?.(draft, ctx);
450
+ if (result !== false) clearPendingChatEntry();
451
+ return result;
452
+ }
453
+ };
454
+ return /* @__PURE__ */ jsx5(
455
+ WorkspaceAgentFront2,
80
456
  {
457
+ ...workspaceProps,
81
458
  workspaceId,
82
459
  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
- }
460
+ authHeaders,
461
+ chatParams,
462
+ bootPreloadPaths,
463
+ frontPluginHotReload: false,
464
+ hotReloadEnabled: false
465
+ },
466
+ workspaceId
98
467
  );
99
468
  }
469
+ function chatFirstPublicPaths(workspaceRoute) {
470
+ return Array.from(/* @__PURE__ */ new Set(["/", workspaceRoute, "/workspace/:id", "/w/:id"]));
471
+ }
100
472
  function CoreWorkspaceAgentFront({
101
473
  authPages,
102
474
  cspNonce,
@@ -106,11 +478,13 @@ function CoreWorkspaceAgentFront({
106
478
  workspaceHref = (workspaceId) => `/workspace/${workspaceId}`,
107
479
  loadingFallback,
108
480
  bootPreloadPaths,
109
- topBarLeft = /* @__PURE__ */ jsx(WorkspaceSwitcher, {}),
110
- topBarRight = /* @__PURE__ */ jsx(DefaultTopBarRight, {}),
481
+ topBarLeft = /* @__PURE__ */ jsx5(WorkspaceSwitcher, {}),
482
+ topBarRight = /* @__PURE__ */ jsx5(DefaultTopBarRight, {}),
111
483
  appTitle = "Boring",
112
484
  bridgeEndpoint = "/api/v1/ui",
113
485
  hotReload = false,
486
+ chatEntryMode = "auth-first",
487
+ chatFirstPublicShell,
114
488
  ...workspaceProps
115
489
  }) {
116
490
  if (hotReload !== false) {
@@ -118,7 +492,7 @@ function CoreWorkspaceAgentFront({
118
492
  "CoreWorkspaceAgentFront does not support hotReload yet; use static plugin consumption or WorkspaceAgentFront for standalone hot reload."
119
493
  );
120
494
  }
121
- const resolvedLoadingFallback = loadingFallback ?? /* @__PURE__ */ jsx(
495
+ const resolvedLoadingFallback = loadingFallback ?? /* @__PURE__ */ jsx5(
122
496
  WorkspaceLoadingPage,
123
497
  {
124
498
  appTitle,
@@ -133,37 +507,55 @@ function CoreWorkspaceAgentFront({
133
507
  topBarRight,
134
508
  bridgeEndpoint
135
509
  };
136
- return /* @__PURE__ */ jsxs(CoreFront, { authPages, cspNonce, children: [
137
- /* @__PURE__ */ jsx(
138
- Route,
139
- {
140
- path: "/",
141
- element: /* @__PURE__ */ jsx(
142
- HomeRedirect,
510
+ return /* @__PURE__ */ jsxs4(
511
+ CoreFront,
512
+ {
513
+ authPages,
514
+ cspNonce,
515
+ workspaceRoute,
516
+ workspaceIdParam,
517
+ publicPaths: chatEntryMode === "chat-first" ? chatFirstPublicPaths(workspaceRoute) : void 0,
518
+ children: [
519
+ /* @__PURE__ */ jsx5(
520
+ Route,
143
521
  {
144
- loadingFallback: resolvedLoadingFallback,
145
- workspaceHref
522
+ path: "/",
523
+ element: /* @__PURE__ */ jsx5(
524
+ HomeRedirect,
525
+ {
526
+ loadingFallback: resolvedLoadingFallback,
527
+ workspaceHref,
528
+ chatEntryMode,
529
+ appTitle,
530
+ workspaceProps: resolvedWorkspaceProps,
531
+ chatFirstPublicShell
532
+ }
533
+ )
146
534
  }
147
- )
148
- }
149
- ),
150
- /* @__PURE__ */ jsx(
151
- Route,
152
- {
153
- path: workspaceRoute,
154
- element: /* @__PURE__ */ jsx(
155
- WorkspaceRoute,
535
+ ),
536
+ /* @__PURE__ */ jsx5(
537
+ Route,
156
538
  {
157
- workspaceIdParam,
158
- loadingFallback: resolvedLoadingFallback,
159
- bootPreloadPaths,
160
- workspaceProps: resolvedWorkspaceProps
539
+ path: workspaceRoute,
540
+ element: /* @__PURE__ */ jsx5(
541
+ WorkspaceRoute,
542
+ {
543
+ workspaceIdParam,
544
+ loadingFallback: resolvedLoadingFallback,
545
+ bootPreloadPaths,
546
+ workspaceProps: resolvedWorkspaceProps,
547
+ chatEntryMode,
548
+ appTitle,
549
+ workspaceRoute,
550
+ chatFirstPublicShell
551
+ }
552
+ )
161
553
  }
162
- )
163
- }
164
- ),
165
- children
166
- ] });
554
+ ),
555
+ children
556
+ ]
557
+ }
558
+ );
167
559
  }
168
560
  export {
169
561
  CoreWorkspaceAgentFront