@hachej/boring-core 0.1.24 → 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.
@@ -2,12 +2,24 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
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';
6
+
7
+ interface ChatFirstPublicShellOptions {
8
+ composerPlaceholder?: string;
9
+ emptyState?: {
10
+ eyebrow?: string;
11
+ title?: string;
12
+ description?: string;
13
+ };
14
+ suggestions?: ChatSuggestion[];
15
+ }
5
16
 
6
17
  type ChatEntryMode = 'auth-first' | 'chat-first';
7
18
  interface CoreWorkspaceAgentFrontProps<TSession extends WorkspaceAgentSession = WorkspaceAgentSession> extends Omit<WorkspaceAgentFrontProps<TSession>, 'workspaceId' | 'frontPluginHotReload' | 'hotReloadEnabled'> {
8
19
  /** Core consumes plugins statically for now; app-level hot reload is explicitly unsupported. */
9
20
  hotReload?: false;
10
21
  chatEntryMode?: ChatEntryMode;
22
+ chatFirstPublicShell?: ChatFirstPublicShellOptions;
11
23
  authPages?: CoreFrontAuthPagesOverride;
12
24
  cspNonce?: string;
13
25
  children?: ReactNode;
@@ -17,6 +29,6 @@ interface CoreWorkspaceAgentFrontProps<TSession extends WorkspaceAgentSession =
17
29
  loadingFallback?: ReactNode;
18
30
  bootPreloadPaths?: string[];
19
31
  }
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;
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;
21
33
 
22
- export { CoreWorkspaceAgentFront, type CoreWorkspaceAgentFrontProps };
34
+ export { type ChatFirstPublicShellOptions, CoreWorkspaceAgentFront, type CoreWorkspaceAgentFrontProps };
@@ -234,6 +234,17 @@ function workspaceIdFromPath(pathname, workspaceRoute, workspaceIdParam) {
234
234
 
235
235
  // src/app/front/chatFirst/ChatFirstPublicShell.tsx
236
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
+ ];
237
248
  function readComposerDraftFromDom() {
238
249
  if (typeof document === "undefined") return "";
239
250
  const input = document.querySelector('[data-boring-agent-part="composer-input"]');
@@ -242,6 +253,7 @@ function readComposerDraftFromDom() {
242
253
  function ChatFirstPublicShell({
243
254
  appTitle,
244
255
  intendedWorkspaceId,
256
+ publicShell,
245
257
  workspaceProps
246
258
  }) {
247
259
  const location = useLocation();
@@ -267,18 +279,12 @@ function ChatFirstPublicShell({
267
279
  chatParams: {
268
280
  ...workspaceProps.chatParams,
269
281
  emptyPlacement: "hero",
270
- composerPlaceholder: "Describe the app, bug, or repo task you want help with\u2026",
282
+ composerPlaceholder: publicShell?.composerPlaceholder ?? "Describe the app, bug, or repo task you want help with\u2026",
271
283
  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."
284
+ ...defaultPublicEmptyState,
285
+ ...publicShell?.emptyState
275
286
  },
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
- ],
287
+ suggestions: publicShell?.suggestions ?? defaultPublicSuggestions,
282
288
  onBeforeSubmit: (draft) => {
283
289
  openAuth(draft);
284
290
  return false;
@@ -347,7 +353,8 @@ function HomeRedirect({
347
353
  workspaceHref,
348
354
  chatEntryMode,
349
355
  appTitle,
350
- workspaceProps
356
+ workspaceProps,
357
+ chatFirstPublicShell
351
358
  }) {
352
359
  const location = useLocation2();
353
360
  const session = useSession();
@@ -359,7 +366,7 @@ function HomeRedirect({
359
366
  location.search,
360
367
  location.hash
361
368
  );
362
- if (!session.data?.user && chatEntryMode === "chat-first") return /* @__PURE__ */ jsx5(ChatFirstPublicShell, { appTitle, workspaceProps });
369
+ if (!session.data?.user && chatEntryMode === "chat-first") return /* @__PURE__ */ jsx5(ChatFirstPublicShell, { appTitle, publicShell: chatFirstPublicShell, workspaceProps });
363
370
  if (!workspace && chatEntryMode === "chat-first" && session.data?.user && restorePendingDraft) {
364
371
  return /* @__PURE__ */ jsx5(
365
372
  ChatFirstAuthenticatedShell,
@@ -388,7 +395,8 @@ function WorkspaceRoute({
388
395
  workspaceProps,
389
396
  chatEntryMode,
390
397
  appTitle,
391
- workspaceRoute
398
+ workspaceRoute,
399
+ chatFirstPublicShell
392
400
  }) {
393
401
  const params = useParams();
394
402
  const location = useLocation2();
@@ -414,7 +422,7 @@ function WorkspaceRoute({
414
422
  );
415
423
  if (!workspaceId) return /* @__PURE__ */ jsx5(Fragment2, { children: loadingFallback });
416
424
  if (!session.data?.user && chatEntryMode === "chat-first") {
417
- return /* @__PURE__ */ jsx5(ChatFirstPublicShell, { appTitle, intendedWorkspaceId: workspaceId, workspaceProps });
425
+ return /* @__PURE__ */ jsx5(ChatFirstPublicShell, { appTitle, intendedWorkspaceId: workspaceId, publicShell: chatFirstPublicShell, workspaceProps });
418
426
  }
419
427
  if (routeStatus.status === "not-found" || routeStatus.status === "forbidden" || routeStatus.status === "switch-failed") {
420
428
  return /* @__PURE__ */ jsx5(WorkspaceRouteErrorPage, { status: routeStatus.status, message: routeStatus.message });
@@ -476,6 +484,7 @@ function CoreWorkspaceAgentFront({
476
484
  bridgeEndpoint = "/api/v1/ui",
477
485
  hotReload = false,
478
486
  chatEntryMode = "auth-first",
487
+ chatFirstPublicShell,
479
488
  ...workspaceProps
480
489
  }) {
481
490
  if (hotReload !== false) {
@@ -518,7 +527,8 @@ function CoreWorkspaceAgentFront({
518
527
  workspaceHref,
519
528
  chatEntryMode,
520
529
  appTitle,
521
- workspaceProps: resolvedWorkspaceProps
530
+ workspaceProps: resolvedWorkspaceProps,
531
+ chatFirstPublicShell
522
532
  }
523
533
  )
524
534
  }
@@ -536,7 +546,8 @@ function CoreWorkspaceAgentFront({
536
546
  workspaceProps: resolvedWorkspaceProps,
537
547
  chatEntryMode,
538
548
  appTitle,
539
- workspaceRoute
549
+ workspaceRoute,
550
+ chatFirstPublicShell
540
551
  }
541
552
  )
542
553
  }
@@ -9,7 +9,7 @@ import {
9
9
  registerRoutes,
10
10
  registerSettingsRoutes,
11
11
  registerWorkspaceRoutes
12
- } from "../../chunk-TAXVSQDW.js";
12
+ } from "../../chunk-2JDK4XUZ.js";
13
13
  import {
14
14
  PostgresUserStore,
15
15
  PostgresWorkspaceStore,
@@ -1955,6 +1955,7 @@ var DEFAULT_WORKSPACE_NAME = "My Workspace";
1955
1955
  var workspaceRoutesPlugin = async (app) => {
1956
1956
  const store = app.workspaceStore;
1957
1957
  const provisioner = app.provisioner;
1958
+ const defaultWorkspaceCreates = /* @__PURE__ */ new Map();
1958
1959
  async function provisionWorkspace(workspace, ownerId, request) {
1959
1960
  if (!provisioner) return;
1960
1961
  await store.putWorkspaceRuntime(workspace.id, { state: "pending" });
@@ -2002,8 +2003,26 @@ var workspaceRoutesPlugin = async (app) => {
2002
2003
  await Promise.all(existing.map((workspace) => ensureDefaultWorkspaceProvisioned(workspace, request)));
2003
2004
  return existing;
2004
2005
  }
2005
- const created = await createWorkspaceForUser(userId, DEFAULT_WORKSPACE_NAME, true, request);
2006
- return [created];
2006
+ const createKey = `${app.config.appId}:${userId}`;
2007
+ const inFlight = defaultWorkspaceCreates.get(createKey);
2008
+ if (inFlight) return await inFlight;
2009
+ const createPromise = (async () => {
2010
+ try {
2011
+ const created = await createWorkspaceForUser(userId, DEFAULT_WORKSPACE_NAME, true, request);
2012
+ return [created];
2013
+ } catch (error) {
2014
+ if (error instanceof HttpError) throw error;
2015
+ const racedExisting = await store.list(userId, app.config.appId);
2016
+ if (racedExisting.length > 0) return racedExisting;
2017
+ throw error;
2018
+ }
2019
+ })();
2020
+ defaultWorkspaceCreates.set(createKey, createPromise);
2021
+ try {
2022
+ return await createPromise;
2023
+ } finally {
2024
+ if (defaultWorkspaceCreates.get(createKey) === createPromise) defaultWorkspaceCreates.delete(createKey);
2025
+ }
2007
2026
  }
2008
2027
  app.post("/api/v1/workspaces", async (request, reply) => {
2009
2028
  const parsed = createWorkspaceBody.safeParse(request.body);
@@ -24,7 +24,7 @@ import {
24
24
  requireWorkspaceMember,
25
25
  validateConfig,
26
26
  validatePasswordStrength
27
- } from "../chunk-TAXVSQDW.js";
27
+ } from "../chunk-2JDK4XUZ.js";
28
28
  import {
29
29
  createDatabase,
30
30
  runMigrations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-core",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Foundation package for boring-ui-v2 apps: DB, auth, config, HTTP app factory, and frontend app shell.",
@@ -79,9 +79,9 @@
79
79
  "react-router-dom": "^7.14.2",
80
80
  "smol-toml": "^1.6.1",
81
81
  "zod": "^3.25.76",
82
- "@hachej/boring-agent": "0.1.24",
83
- "@hachej/boring-workspace": "0.1.24",
84
- "@hachej/boring-ui-kit": "0.1.24"
82
+ "@hachej/boring-ui-kit": "0.1.26",
83
+ "@hachej/boring-workspace": "0.1.26",
84
+ "@hachej/boring-agent": "0.1.26"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@testing-library/jest-dom": "^6.9.1",