@k2b/cloud 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/package.json +5 -5
  2. package/src/_internal/app-presentation.ts +40 -11
  3. package/src/_internal/define-app.ts +18 -8
  4. package/src/_internal/page-responses.ts +5 -1
  5. package/src/_internal/registry.ts +10 -1
  6. package/src/_internal/widget-declarations.ts +16 -3
  7. package/src/_internal/widget-settings.ts +93 -0
  8. package/src/ai/capabilities.ts +8 -5
  9. package/src/ai/code-source-contracts.ts +17 -2
  10. package/src/ai/code-source-tools.ts +1 -0
  11. package/src/ai/default-tools.ts +4 -4
  12. package/src/ai/memories.ts +48 -1
  13. package/src/ai/memory-learning.ts +17 -6
  14. package/src/ai/memory-tool.ts +1 -0
  15. package/src/ai/migrate.ts +8 -0
  16. package/src/ai/provider.ts +4 -3
  17. package/src/ai/resource-refs.ts +35 -3
  18. package/src/ai/system-prompt.ts +3 -2
  19. package/src/api/index.ts +2 -0
  20. package/src/api/mcp.ts +48 -17
  21. package/src/api/pwa.ts +7 -2
  22. package/src/api/resource-links.ts +43 -0
  23. package/src/api/widgets.ts +163 -82
  24. package/src/browser/CloudResourceSearch.tsx +6 -6
  25. package/src/browser/command-shortcuts.ts +2 -11
  26. package/src/browser/navigation-search.ts +4 -1
  27. package/src/browser/resource-picker.tsx +1 -0
  28. package/src/browser/resource-search.ts +7 -2
  29. package/src/browser/widgets.ts +23 -5
  30. package/src/capabilities/testing.ts +13 -1
  31. package/src/cli/capabilities.ts +61 -17
  32. package/src/cli/index.ts +2 -0
  33. package/src/cli/resource-links.ts +13 -0
  34. package/src/contracts/app.ts +7 -1
  35. package/src/contracts/capabilities.ts +25 -5
  36. package/src/contracts/registry.ts +3 -1
  37. package/src/contracts/resource-href.ts +7 -0
  38. package/src/contracts/widgets.ts +80 -6
  39. package/src/desktop/index.ts +3 -2
  40. package/src/desktop/solid.tsx +7 -5
  41. package/src/server/widget-request.ts +34 -5
  42. package/src/services/app-approval.ts +4 -2
  43. package/src/services/approvals/index.ts +40 -11
  44. package/src/services/approvals/list.ts +1 -0
  45. package/src/services/approvals/migrate.ts +10 -0
  46. package/src/services/outbox.ts +13 -6
  47. package/src/services/pdf/markdown.ts +11 -7
  48. package/src/services/pwa-devices.ts +68 -15
  49. package/src/shared/ai-platform-prompt.ts +3 -2
  50. package/src/shared/app-presentation.ts +49 -1
  51. package/src/shared/index.ts +1 -0
  52. package/src/shared/markdown/callout-sanitizer.ts +37 -0
  53. package/src/shared/markdown/index.ts +8 -2
  54. package/src/shared/theme.ts +41 -20
  55. package/src/ssr/AnnouncementBanners.tsx +30 -0
  56. package/src/ssr/AppLaunchpadPanel.tsx +2 -2
  57. package/src/ssr/CloudShell.island.tsx +91 -0
  58. package/src/ssr/GlobalSearchTrigger.tsx +51 -0
  59. package/src/ssr/Layout.tsx +58 -99
  60. package/src/ssr/LayoutHeader.tsx +31 -77
  61. package/src/ssr/LayoutHelp.browser-harness.tsx +43 -0
  62. package/src/ssr/LayoutHelp.tsx +319 -238
  63. package/src/ssr/LayoutHelpTrigger.tsx +49 -0
  64. package/src/ssr/LayoutPreferences.tsx +31 -0
  65. package/src/ssr/LayoutRail.tsx +15 -69
  66. package/src/ssr/MinimalLayout.tsx +2 -3
  67. package/src/ssr/MinimalLayoutPreferences.island.tsx +6 -15
  68. package/src/ssr/MobileNavigation.tsx +13 -8
  69. package/src/ssr/MobileProfileActions.tsx +4 -6
  70. package/src/ssr/PageTrailRow.tsx +14 -0
  71. package/src/ssr/{ProfilePreferences.island.tsx → ProfilePreferences.tsx} +8 -5
  72. package/src/ssr/PwaLayout.tsx +0 -2
  73. package/src/ssr/PwaRuntime.island.tsx +1 -1
  74. package/src/ssr/ShellRuntime.island.tsx +166 -0
  75. package/src/ssr/ThemeCycleButton.tsx +46 -0
  76. package/src/ssr/TimezoneCookie.island.tsx +3 -19
  77. package/src/ssr/WorkspaceNavigationProvider.tsx +20 -1
  78. package/src/ssr/announcements-dialog.tsx +66 -0
  79. package/src/ssr/app-appearance.ts +6 -1
  80. package/src/ssr/{AppLaunchpad.island.tsx → app-launchpad.tsx} +42 -62
  81. package/src/ssr/busy-action.ts +19 -0
  82. package/src/ssr/islands/index.ts +9 -6
  83. package/src/ssr/launchpad-surface.tsx +15 -0
  84. package/src/ssr/layout-help-registry.tsx +60 -0
  85. package/src/ssr/layout-help-url.ts +39 -0
  86. package/src/ssr/mobile-navigation.browser-harness.tsx +20 -0
  87. package/src/ssr/platform-messages.ts +0 -8
  88. package/src/ssr/preference-controller.ts +62 -19
  89. package/src/ssr/profile-actions.ts +12 -5
  90. package/src/ssr/profile-preferences-messages.ts +10 -4
  91. package/src/ssr/rail-context.ts +8 -12
  92. package/src/ssr/rail-navigation.ts +3 -0
  93. package/src/ssr/shell-data.tsx +48 -0
  94. package/src/ssr/timezone-cookie.ts +22 -0
  95. package/src/ssr/workspace-navigation.ts +25 -2
  96. package/src/styles/effects.css +13 -3
  97. package/src/styles/global.css +43 -5
  98. package/src/styles/input.css +0 -8
  99. package/src/styles/tokens.css +0 -2
  100. package/src/styles/utilities-navigation.css +10 -0
  101. package/src/workflows/language/compiler.ts +1 -1
  102. package/src/ssr/AppWorkspaceController.island.tsx +0 -21
  103. package/src/ssr/BrowserPushRegistration.island.tsx +0 -12
  104. package/src/ssr/GlobalAnnouncements.island.tsx +0 -117
  105. package/src/ssr/GlobalSearchHelpDialog.tsx +0 -104
  106. package/src/ssr/GlobalSearchTrigger.island.tsx +0 -75
  107. package/src/ssr/LayoutHelpTrigger.island.tsx +0 -61
  108. package/src/ssr/LayoutPreferences.island.tsx +0 -21
  109. package/src/ssr/PageTrailRow.island.tsx +0 -20
  110. package/src/ssr/PageTrailRuntime.island.tsx +0 -26
  111. package/src/ssr/RegisteredHelpDocuments.island.tsx +0 -3
  112. /package/src/ssr/{LayoutBreadcrumbs.island.tsx → LayoutBreadcrumbs.tsx} +0 -0
  113. /package/src/ssr/{RailApps.island.tsx → RailApps.tsx} +0 -0
@@ -2,7 +2,6 @@ import type { DateContext } from "@k2b/stdlib";
2
2
  import { AppWorkspace, appWorkspaceLayoutStyle, LocaleProvider, NoticeCard, PageTrailProvider } from "@k2b/ui";
3
3
  import type { JSX } from "solid-js/jsx-runtime";
4
4
  import { readAppWorkspaceLayoutCookie, resolveAppWorkspaceLayoutForSidebar } from "../_internal/app-workspace-state";
5
- import { navigationSearchItems } from "../browser/navigation-search";
6
5
  import { resolveNavMatch } from "../contracts/app"; // ==========================
7
6
  import { isPwaShellAvailable } from "../contracts/pwa";
8
7
  import { defaultRailPreferences } from "../contracts/rail-preferences";
@@ -10,29 +9,24 @@ import { hasRole, type User } from "../contracts/shared";
10
9
  import { getLocale } from "../server/locale";
11
10
  import { getDateConfig } from "../server/time";
12
11
  import { dates } from "../shared";
13
- import { readThemeFromCookieHeader } from "../shared/theme";
14
- import type { AppLaunchpadApp } from "./AppLaunchpad.island";
15
- import AppWorkspaceController from "./AppWorkspaceController.island";
16
- import { appAppearanceStyle } from "./app-appearance";
12
+ import { readThemePreferenceFromCookieHeader } from "../shared/theme";
13
+ import AnnouncementBanners from "./AnnouncementBanners";
14
+ import { appAppearanceStyle, resolveCurrentApp } from "./app-appearance";
17
15
  import { resolveRuntimeRoute, visibleNavigationApps } from "./app-navigation";
18
- import BrowserPushRegistration from "./BrowserPushRegistration.island";
19
- import GlobalAnnouncements from "./GlobalAnnouncements.island";
20
- import type { GlobalSearchHelpApp } from "./GlobalSearchHelpDialog";
21
16
  import { getLayoutHelp } from "./help";
22
17
  import LayoutFooter from "./LayoutFooter";
23
18
  import LayoutHeader from "./LayoutHeader";
24
19
  import LayoutRail, { type LayoutAppLink } from "./LayoutRail";
25
20
  import type { LayoutContext } from "./layout-context";
26
21
  import type { LayoutBreadcrumb } from "./layout-runtime";
27
- import PageTrailRow from "./PageTrailRow.island";
28
- import PageTrailRuntime from "./PageTrailRuntime.island";
22
+ import PageTrailRow from "./PageTrailRow";
29
23
  import { derivePageTrail } from "./page-trail";
30
24
  import { platformMessages } from "./platform-messages";
31
- import RegisteredHelpDocuments from "./RegisteredHelpDocuments.island";
32
25
  import { sortRailApps } from "./rail-navigation";
33
26
  import { requestPath } from "./request-path";
34
27
  import { getLocalizedRuntimeContext, type RuntimeContext } from "./runtime";
35
- import TimezoneCookie from "./TimezoneCookie.island";
28
+ import ShellRuntime from "./ShellRuntime.island";
29
+ import { type ShellData, ShellDataProvider } from "./shell-data";
36
30
 
37
31
  // Types
38
32
  type Breadcrumb = LayoutBreadcrumb;
@@ -146,8 +140,7 @@ export default function Layout(props: LayoutProps) {
146
140
  const { c, title, fullPage, fullWidth, focusMode, flushCanvas, workspaceSidebarCollapsible } = props;
147
141
  const runtime = getLocalizedRuntimeContext(c);
148
142
  const cookie = c.req.raw.headers.get("Cookie") ?? "";
149
- const theme = readThemeFromCookieHeader(cookie);
150
- c.get("page").theme = theme;
143
+ const theme = readThemePreferenceFromCookieHeader(cookie);
151
144
  // One request-scoped locale drives <html lang>, the LocaleProvider below,
152
145
  // and date formatting. The SSR seam and this component both resolve through
153
146
  // the same canonical getLocale(c), so pages cannot diverge the three seams.
@@ -157,38 +150,19 @@ export default function Layout(props: LayoutProps) {
157
150
  const user = c.get("user");
158
151
  const pathname = new URL(c.req.raw.url).pathname;
159
152
  const currentRoute = resolveRuntimeRoute(runtime.apps, pathname);
160
- const currentApp = currentRoute?.app;
153
+ const currentApp = resolveCurrentApp(runtime.apps, pathname);
161
154
  const registeredHelp = getLayoutHelp(c);
155
+ const runtimeHelp = registeredHelp ? { documents: registeredHelp.documents, pageBase: registeredHelp.pageBase } : undefined;
162
156
  const workspaceLayout = resolveAppWorkspaceLayoutForSidebar(
163
157
  readAppWorkspaceLayoutCookie(cookie, currentApp?.id),
164
158
  workspaceSidebarCollapsible,
165
159
  );
166
160
  const { primary: primaryApps, more: moreApps } = buildNavLinks(runtime.apps, user, lang);
167
161
  const allApps = sortRailApps([...primaryApps, ...moreApps], lang);
168
- const launchpadApps: AppLaunchpadApp[] = allApps.map((app) => ({
169
- id: app.id,
170
- iconClass: app.iconClass,
171
- label: app.label,
172
- href: app.href,
173
- description: app.description,
174
- accent: app.accent,
175
- badge: app.badge,
176
- }));
177
- const searchLinks = navigationSearchItems(
178
- runtime.apps,
179
- allApps.map((app) => app.id),
162
+ const visibleAppIds = new Set(allApps.map((app) => app.id));
163
+ const searchLinks = Object.fromEntries(
164
+ runtime.apps.flatMap((app) => (visibleAppIds.has(app.id) && app.searchLinks?.length ? [[app.id, app.searchLinks] as const] : [])),
180
165
  );
181
- const searchHelpApps: GlobalSearchHelpApp[] = runtime.apps
182
- .filter((app) => (app.searchTags?.length ?? 0) > 0)
183
- .map((app) => ({
184
- appId: app.id,
185
- appName: app.name,
186
- appIcon: app.icon,
187
- help: app.searchHelp,
188
- tags: [...new Set((app.searchTags ?? []).map((tag) => tag.toLowerCase()))],
189
- tagHelp: [...(app.searchTagHelp ?? [])],
190
- }))
191
- .sort((a, b) => a.appName.localeCompare(b.appName));
192
166
  const settings = c.get("settings");
193
167
  const announcements = c.get("announcements");
194
168
  const appName = settings?.app?.name || "Cloud";
@@ -227,6 +201,18 @@ export default function Layout(props: LayoutProps) {
227
201
  [appAppearanceStyle(currentApp?.appearance), appWorkspaceLayoutStyle(workspaceLayout), focusMode && flushCanvas ? "padding:0" : ""]
228
202
  .filter(Boolean)
229
203
  .join(";") || undefined;
204
+ const appLabel = currentApp?.name ?? (pathname.startsWith("/admin") ? t.admin : undefined);
205
+ const shellData: ShellData = {
206
+ apps: allApps,
207
+ ...(user
208
+ ? { rail: c.get("railPreferences") ?? defaultRailPreferences(), profile: { name: profileName, avatarSrc: profileAvatarSrc, appHref } }
209
+ : {}),
210
+ legalLinks,
211
+ theme,
212
+ accent: currentApp?.appearance?.accent,
213
+ searchLinks,
214
+ appId: currentApp?.id,
215
+ };
230
216
  if (focusMode) {
231
217
  return (
232
218
  <LocaleProvider locale={lang}>
@@ -239,10 +225,8 @@ export default function Layout(props: LayoutProps) {
239
225
  data-layout-full-page="true"
240
226
  data-workspace-sidebar-collapsed={workspaceLayout?.sidebarCollapsed ? "true" : undefined}
241
227
  >
242
- <TimezoneCookie />
243
- {registeredHelp && <RegisteredHelpDocuments documents={registeredHelp.documents} pageBase={registeredHelp.pageBase} />}
228
+ <ShellRuntime pageTrail help={runtimeHelp} />
244
229
  <PageTrailProvider trail={pageTrail}>
245
- <PageTrailRuntime />
246
230
  <main class="min-h-0 min-w-0 flex-1">
247
231
  <AppWorkspace.LayoutStateProvider state={workspaceLayout}>{props.children}</AppWorkspace.LayoutStateProvider>
248
232
  </main>
@@ -266,65 +250,40 @@ export default function Layout(props: LayoutProps) {
266
250
  data-layout-authenticated={showRail ? "true" : undefined}
267
251
  data-workspace-sidebar-collapsed={workspaceLayout?.sidebarCollapsed ? "true" : undefined}
268
252
  >
269
- <TimezoneCookie />
270
- {registeredHelp && <RegisteredHelpDocuments documents={registeredHelp.documents} pageBase={registeredHelp.pageBase} />}
271
- <AppWorkspaceController appId={currentApp?.id} />
272
- {user && <BrowserPushRegistration />}
273
- {showRail && (
274
- <LayoutRail
275
- accent={currentApp?.appearance?.accent}
276
- appHref={appHref}
277
- appsLabel={t.apps}
278
- homeLabel={t.home}
279
- launchpadApps={launchpadApps}
280
- legalLinks={legalLinks}
281
- openAppsLabel={t.openApps}
282
- apps={allApps}
283
- railSettings={c.get("railPreferences") ?? defaultRailPreferences()}
284
- currentPath={requestPath(c)}
285
- profileAvatarSrc={profileAvatarSrc}
286
- profileName={profileName}
287
- searchHelpApps={searchHelpApps}
288
- searchLinks={searchLinks}
289
- theme={theme}
290
- />
291
- )}
292
- <div class="layout-shell-content flex min-h-0 min-w-0 flex-1 flex-col">
293
- <LayoutHeader
294
- appHref={appHref}
295
- appLabel={currentApp?.name ?? (pathname.startsWith("/admin") ? t.admin : undefined)}
296
- accent={currentApp?.appearance?.accent}
297
- authenticated={showRail}
298
- breadcrumbs={breadcrumbs}
299
- homeLabel={t.home}
300
- launchpadApps={launchpadApps}
301
- legalLinks={legalLinks}
302
- openAppsLabel={t.openApps}
303
- profileName={profileName}
304
- searchHelpApps={searchHelpApps}
305
- searchLinks={searchLinks}
306
- signInLabel={t.signIn}
307
- theme={theme}
308
- />
309
- {user && announcements && (
310
- <GlobalAnnouncements
311
- banners={announcements.banners}
312
- announcements={announcements.announcements}
313
- latestAnnouncementVersion={announcements.latestAnnouncementVersion}
314
- cookieState={announcements.cookieState}
253
+ <ShellRuntime
254
+ workspace={{ appId: currentApp?.id }}
255
+ pageTrail
256
+ help={runtimeHelp}
257
+ commands={{ searchResources: !!user }}
258
+ push={!!user}
259
+ announcements={
260
+ user && announcements?.announcements.length
261
+ ? { entries: announcements.announcements, latestVersion: announcements.latestAnnouncementVersion }
262
+ : undefined
263
+ }
264
+ banners={user && announcements?.banners.length ? true : undefined}
265
+ />
266
+ <ShellDataProvider data={shellData}>
267
+ {showRail && <LayoutRail currentPath={requestPath(c)} />}
268
+ <div class="layout-shell-content flex min-h-0 min-w-0 flex-1 flex-col">
269
+ <LayoutHeader
270
+ authenticated={showRail}
271
+ title={appLabel ?? breadcrumbs.at(-1)?.title}
272
+ launchpad={!!(user || appLabel)}
273
+ breadcrumbs={user ? undefined : breadcrumbs}
315
274
  />
316
- )}
317
- {user && <ProfileWarnings user={user} locale={lang} />}
318
- {user && <ExpiryWarnings user={user} dateConfig={dateConfig} locale={lang} />}
319
- <PageTrailProvider trail={pageTrail}>
320
- <PageTrailRuntime />
321
- <PageTrailRow />
322
- <main class={`layout-content-main min-h-0 min-w-0 flex-1 ${mainLayoutClass}`}>
323
- <AppWorkspace.LayoutStateProvider state={workspaceLayout}>{props.children}</AppWorkspace.LayoutStateProvider>
324
- </main>
325
- </PageTrailProvider>
326
- {!fullPage && !showRail && <LayoutFooter appName={settings?.app?.copyright || appName} legalLinks={legalLinks} />}
327
- </div>
275
+ {user && announcements && <AnnouncementBanners banners={announcements.banners} />}
276
+ {user && <ProfileWarnings user={user} locale={lang} />}
277
+ {user && <ExpiryWarnings user={user} dateConfig={dateConfig} locale={lang} />}
278
+ <PageTrailProvider trail={pageTrail}>
279
+ <PageTrailRow trail={pageTrail} />
280
+ <main class={`layout-content-main min-h-0 min-w-0 flex-1 ${mainLayoutClass}`}>
281
+ <AppWorkspace.LayoutStateProvider state={workspaceLayout}>{props.children}</AppWorkspace.LayoutStateProvider>
282
+ </main>
283
+ </PageTrailProvider>
284
+ {!fullPage && !showRail && <LayoutFooter appName={settings?.app?.copyright || appName} legalLinks={legalLinks} />}
285
+ </div>
286
+ </ShellDataProvider>
328
287
  </div>
329
288
  </LocaleProvider>
330
289
  );
@@ -1,30 +1,13 @@
1
- import { ButtonLink, IconButtonLink } from "@k2b/ui";
2
- import type { NavigationSearchItem } from "../browser/navigation-search";
3
- import type { CloudTheme } from "../shared/theme";
4
- import AppLaunchpad, { type AppLaunchpadApp } from "./AppLaunchpad.island";
5
- import type { GlobalSearchHelpApp } from "./GlobalSearchHelpDialog";
6
- import GlobalSearchTrigger from "./GlobalSearchTrigger.island";
7
- import LayoutBreadcrumbs from "./LayoutBreadcrumbs.island";
8
- import LayoutHelpTrigger from "./LayoutHelpTrigger.island";
9
- import LayoutPreferences from "./LayoutPreferences.island";
1
+ import { ButtonLink, IconButtonLink, useLocale } from "@k2b/ui";
2
+ import CloudShell from "./CloudShell.island";
10
3
  import type { LayoutBreadcrumb } from "./layout-runtime";
4
+ import { platformMessages } from "./platform-messages";
11
5
 
12
6
  type LayoutHeaderProps = {
13
- accent?: string;
14
- /** Set while the installation offers the mobile app. */
15
- appHref?: string;
16
- appLabel?: string;
17
7
  authenticated: boolean;
18
- breadcrumbs: LayoutBreadcrumb[];
19
- homeLabel: string;
20
- launchpadApps: AppLaunchpadApp[];
21
- legalLinks: Array<{ label: string; href: string; icon?: string }>;
22
- openAppsLabel: string;
23
- profileName: string;
24
- searchHelpApps: GlobalSearchHelpApp[];
25
- searchLinks?: NavigationSearchItem[];
26
- signInLabel: string;
27
- theme: CloudTheme;
8
+ title?: string;
9
+ launchpad: boolean;
10
+ breadcrumbs?: LayoutBreadcrumb[];
28
11
  };
29
12
 
30
13
  /**
@@ -33,68 +16,39 @@ type LayoutHeaderProps = {
33
16
  * and their profile through the rail, so the app area starts at the top edge.
34
17
  * Anonymous pages have no rail: up to `lg` their header keeps the app name,
35
18
  * help, search, and the app grid, and from `lg` it shows the breadcrumbs.
19
+ * Home and Sign in stay server markup, so focus on them survives the shell's
20
+ * mount; the shell renders the title and the controls between them.
36
21
  */
37
22
  export default function LayoutHeader(props: LayoutHeaderProps) {
23
+ const locale = useLocale();
24
+ const t = () => platformMessages.resolve([locale()]).t;
38
25
  return (
39
26
  <header
40
- class={`layout-header paper flex min-h-[2.875rem] shrink-0 items-center justify-between px-2 py-1.5 md:px-3 md:py-2 ${props.authenticated ? "md:hidden" : ""}`}
27
+ class={`layout-header paper flex min-h-[2.875rem] shrink-0 items-center px-2 py-1.5 md:px-3 md:py-2 ${props.authenticated ? "md:hidden" : ""}`}
41
28
  style="box-shadow: var(--ui-shadow-surface)"
42
29
  >
43
- <div class="flex min-w-0 items-center gap-2">
44
- {/* A long title must not squeeze the button; `shrink-0` sits on a wrapper because the button's own
45
- styles would win over a utility on the link. */}
46
- <div class="flex shrink-0">
47
- <IconButtonLink href="/" label={props.homeLabel}>
48
- <img src="/branding/logo" alt="" class={props.authenticated ? "h-4 w-4" : "h-6 w-6"} />
49
- </IconButtonLink>
50
- </div>
51
- {!props.authenticated && (
52
- <div class="hidden min-w-0 items-center lg:flex">
53
- <LayoutBreadcrumbs breadcrumbs={props.breadcrumbs} />
54
- </div>
55
- )}
56
- <div class="flex min-w-0 items-center lg:hidden">
57
- <span class="truncate font-semibold">{props.appLabel ?? props.breadcrumbs.at(-1)?.title}</span>
58
- </div>
30
+ {/* A long title must not squeeze the button; `shrink-0` sits on a wrapper because the button's own
31
+ styles would win over a utility on the link. */}
32
+ <div class="flex shrink-0">
33
+ <IconButtonLink href="/" label={t().home}>
34
+ <img src="/branding/logo" alt="" class={props.authenticated ? "h-4 w-4" : "h-6 w-6"} />
35
+ </IconButtonLink>
59
36
  </div>
60
- <div class="flex shrink-0 items-center gap-1">
61
- <div class="flex items-center gap-1 lg:hidden">
62
- <LayoutHelpTrigger
63
- variant="header"
64
- registerCommand={!props.authenticated}
65
- searchHelpApps={props.searchHelpApps}
66
- accent={props.accent}
67
- />
68
- <GlobalSearchTrigger
69
- variant={props.authenticated ? "header" : "host"}
70
- registerCommand
71
- searchLinks={props.searchLinks}
72
- searchResources={props.authenticated}
73
- />
74
- {(props.authenticated || props.appLabel) && (
75
- <AppLaunchpad
76
- profile={
77
- props.authenticated
78
- ? { name: props.profileName, theme: props.theme, ...(props.appHref ? { appHref: props.appHref } : {}) }
79
- : undefined
80
- }
81
- apps={props.launchpadApps}
82
- legalLinks={props.legalLinks}
83
- variant="header"
84
- label={props.openAppsLabel}
85
- />
86
- )}
37
+ <CloudShell
38
+ slot="header"
39
+ authenticated={props.authenticated}
40
+ title={props.title}
41
+ launchpad={props.launchpad}
42
+ breadcrumbs={props.breadcrumbs}
43
+ />
44
+ {!props.authenticated && (
45
+ <div class="ml-1 flex shrink-0">
46
+ <ButtonLink href="/auth/login" size="sm" variant="primary">
47
+ <i class="ti ti-login" aria-hidden="true" />
48
+ {t().signIn}
49
+ </ButtonLink>
87
50
  </div>
88
- {!props.authenticated && (
89
- <>
90
- <LayoutPreferences initialTheme={props.theme} position="bottom-left" />
91
- <ButtonLink href="/auth/login" size="sm" variant="primary">
92
- <i class="ti ti-login" aria-hidden="true" />
93
- {props.signInLabel}
94
- </ButtonLink>
95
- </>
96
- )}
97
- </div>
51
+ )}
98
52
  </header>
99
53
  );
100
54
  }
@@ -0,0 +1,43 @@
1
+ import { LocaleProvider } from "@k2b/ui";
2
+ import { render } from "solid-js/web";
3
+ import type { HelpDocumentManifest, HelpDocumentPayload } from "../shared/help";
4
+ import { LayoutHelpPage } from "./LayoutHelp";
5
+
6
+ const documents: HelpDocumentManifest[] = Array.from({ length: 8 }, (_, index) => ({
7
+ id: `topic-${index}`,
8
+ title: `Topic ${index}`,
9
+ description: "Help browser fixture",
10
+ order: index,
11
+ url: `/api/help/topic-${index}`,
12
+ searchUrl: "/api/help/search",
13
+ }));
14
+ const html = Array.from(
15
+ { length: 8 },
16
+ (_, index) =>
17
+ `<h2 id="section-${index}" class="help-section-title" data-help-icon="ti ti-point">Section ${index}</h2>${Array.from({ length: 18 }, () => "<p>Read this guidance to finish the task and understand how the app works.</p>").join("")}`,
18
+ ).join("");
19
+ window.fetch = Object.assign(
20
+ async (input: URL | RequestInfo) => {
21
+ await new Promise((resolve) => setTimeout(resolve, 80));
22
+ const path = String(input);
23
+ if (path.includes("/search")) return Response.json({ locale: "en", ids: ["topic-0", "topic-1"] });
24
+ const id = path.split("/").at(-1) ?? "topic-0";
25
+ const payload: HelpDocumentPayload = { id, locale: "en", title: id, markdown: "Help", html };
26
+ return Response.json(payload);
27
+ },
28
+ { preconnect: () => {} },
29
+ );
30
+ const main = document.querySelector<HTMLElement>(".layout-content-main");
31
+ if (!main) throw new Error("Missing Layout main");
32
+ const embedded = new URL(location.href).searchParams.has("embedded");
33
+ const host = document.createElement("div");
34
+ if (embedded) host.style.cssText = "width:360px;height:700px";
35
+ main.append(host);
36
+ render(
37
+ () => (
38
+ <LocaleProvider locale="en">
39
+ <LayoutHelpPage documents={documents} embedded={embedded} pageBase="/app/spaces/help" />
40
+ </LocaleProvider>
41
+ ),
42
+ host,
43
+ );