@yourrealm/sdk 0.0.0-alpha.1378.ce03c8dc

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 (230) hide show
  1. package/LICENSE-EXCEPTION +26 -0
  2. package/README.md +34 -0
  3. package/dist/chunk-3R6KEFOI.js +22 -0
  4. package/dist/chunk-67BDAMYX.js +376 -0
  5. package/dist/chunk-AITGS27R.js +139 -0
  6. package/dist/chunk-CMAEHQ4F.js +6 -0
  7. package/dist/chunk-MLKGABMK.js +9 -0
  8. package/dist/chunk-O3UIK34B.js +1430 -0
  9. package/dist/chunk-VHTUSZUX.js +176 -0
  10. package/dist/cli.js +9006 -0
  11. package/dist/docker.js +54 -0
  12. package/dist/index.js +1053 -0
  13. package/dist/ingress.js +436 -0
  14. package/dist/jsx-runtime.js +2 -0
  15. package/dist/preact.js +22 -0
  16. package/dist/service-url.js +11 -0
  17. package/dist/services.js +213 -0
  18. package/dist/test.js +5069 -0
  19. package/dist/ui.js +254 -0
  20. package/dist/worker-protocol.js +89 -0
  21. package/package.json +112 -0
  22. package/src/app-icon.ts +74 -0
  23. package/src/apply.ts +52 -0
  24. package/src/bundle-ui.ts +211 -0
  25. package/src/bundle.ts +546 -0
  26. package/src/cli/build-dev-landing.ts +84 -0
  27. package/src/cli/build-install-panel.ts +90 -0
  28. package/src/cli/build-runtime.ts +133 -0
  29. package/src/cli/build.ts +63 -0
  30. package/src/cli/dev.ts +107 -0
  31. package/src/cli/down.ts +60 -0
  32. package/src/cli/main.ts +56 -0
  33. package/src/cli/test.ts +61 -0
  34. package/src/cli/validate.ts +37 -0
  35. package/src/compile.ts +100 -0
  36. package/src/create-app.ts +982 -0
  37. package/src/deep-merge.ts +33 -0
  38. package/src/dev/fixture.ts +48 -0
  39. package/src/dev/install-handler.ts +383 -0
  40. package/src/dev/server.ts +968 -0
  41. package/src/dev/stack.ts +108 -0
  42. package/src/dev/state.ts +159 -0
  43. package/src/docker.ts +96 -0
  44. package/src/errors.ts +61 -0
  45. package/src/factory.ts +481 -0
  46. package/src/garage.ts +56 -0
  47. package/src/generator/hash.ts +6 -0
  48. package/src/generator/resolve-refs.ts +207 -0
  49. package/src/grants-builder.ts +100 -0
  50. package/src/grants.ts +260 -0
  51. package/src/home-api.ts +172 -0
  52. package/src/home-cookie.ts +10 -0
  53. package/src/home-registry.ts +120 -0
  54. package/src/home.ts +187 -0
  55. package/src/host-registry.ts +143 -0
  56. package/src/http-header.ts +20 -0
  57. package/src/identifiers.ts +49 -0
  58. package/src/index.ts +66 -0
  59. package/src/info.ts +64 -0
  60. package/src/ini.ts +50 -0
  61. package/src/json.ts +14 -0
  62. package/src/jsx-runtime/index.ts +5 -0
  63. package/src/keys.ts +29 -0
  64. package/src/mysql.ts +56 -0
  65. package/src/pack-build.ts +108 -0
  66. package/src/pack.ts +115 -0
  67. package/src/peer-registry.ts +121 -0
  68. package/src/peer-world.ts +117 -0
  69. package/src/permission-meta.ts +116 -0
  70. package/src/permissions.ts +108 -0
  71. package/src/pkg-root.ts +34 -0
  72. package/src/postgres.ts +79 -0
  73. package/src/preact/index.ts +27 -0
  74. package/src/redis.ts +43 -0
  75. package/src/ref.ts +271 -0
  76. package/src/remote-registry.ts +33 -0
  77. package/src/required-host-kinds.ts +110 -0
  78. package/src/resolve-context.ts +111 -0
  79. package/src/resolve.ts +471 -0
  80. package/src/runtime/docker/compose/compose-schema.ts +118 -0
  81. package/src/runtime/docker/compose/convert-to-docker-compose.ts +278 -0
  82. package/src/runtime/docker/compose/default-hardening.ts +21 -0
  83. package/src/runtime/docker/compose/effects.ts +107 -0
  84. package/src/runtime/docker/compose/index.ts +17 -0
  85. package/src/runtime/docker/compose/labels.ts +39 -0
  86. package/src/runtime/docker/compose/types.ts +78 -0
  87. package/src/runtime/docker/home-proxy-server/index.ts +177 -0
  88. package/src/runtime/docker/home-proxy-server/protocol.ts +53 -0
  89. package/src/runtime/docker/ingress/build-config.ts +819 -0
  90. package/src/runtime/docker/ingress/index.ts +16 -0
  91. package/src/runtime/docker/ingress/types.ts +182 -0
  92. package/src/runtime/docker/log-frames.ts +121 -0
  93. package/src/runtime/docker/logs-server/index.ts +366 -0
  94. package/src/runtime/docker/spawn-server/context-key.ts +82 -0
  95. package/src/runtime/docker/spawn-server/index.ts +492 -0
  96. package/src/runtime/docker/spawn-server/protocol.ts +70 -0
  97. package/src/runtime/docker/spawn-server/spec.ts +122 -0
  98. package/src/runtime/docker/stats-server/disks.ts +161 -0
  99. package/src/runtime/docker/stats-server/index.ts +322 -0
  100. package/src/runtime/home-client/index.ts +71 -0
  101. package/src/runtime/log-line.ts +24 -0
  102. package/src/runtime/logs-client/index.ts +175 -0
  103. package/src/runtime/spawn-client/index.ts +93 -0
  104. package/src/runtime/spawn-client/kaniko.ts +79 -0
  105. package/src/runtime/stats-client/index.ts +65 -0
  106. package/src/runtime/stats-sample.ts +95 -0
  107. package/src/safe-json.ts +37 -0
  108. package/src/sandbox.ts +1209 -0
  109. package/src/schema/build-apps.ts +38 -0
  110. package/src/schema/home.ts +217 -0
  111. package/src/schema/host.ts +792 -0
  112. package/src/schema/inputs.ts +26 -0
  113. package/src/schema/nodes.ts +75 -0
  114. package/src/schema/peer.ts +400 -0
  115. package/src/schema/public-domain.ts +116 -0
  116. package/src/schema/remote.ts +74 -0
  117. package/src/security.ts +594 -0
  118. package/src/serialize.ts +21 -0
  119. package/src/service-definition.ts +284 -0
  120. package/src/service-url.ts +64 -0
  121. package/src/service.ts +39 -0
  122. package/src/services/index.ts +5 -0
  123. package/src/shared.ts +65 -0
  124. package/src/speculative-silo.ts +96 -0
  125. package/src/stable-secret.ts +14 -0
  126. package/src/surfaces.ts +264 -0
  127. package/src/tailwind-bin.ts +56 -0
  128. package/src/test/boot.ts +192 -0
  129. package/src/test/compile.ts +501 -0
  130. package/src/test/describe.ts +117 -0
  131. package/src/test/index.ts +11 -0
  132. package/src/test/ingress-fixture.ts +37 -0
  133. package/src/test/types.ts +318 -0
  134. package/src/trusted-headers.ts +79 -0
  135. package/src/types.ts +126 -0
  136. package/src/ui/components/aside.tsx +96 -0
  137. package/src/ui/components/badge.tsx +42 -0
  138. package/src/ui/components/button.tsx +59 -0
  139. package/src/ui/components/card.tsx +106 -0
  140. package/src/ui/components/checkbox.tsx +37 -0
  141. package/src/ui/components/code-block.tsx +29 -0
  142. package/src/ui/components/combobox.tsx +119 -0
  143. package/src/ui/components/config-field.tsx +284 -0
  144. package/src/ui/components/dialog.tsx +137 -0
  145. package/src/ui/components/domain-editor.tsx +317 -0
  146. package/src/ui/components/domain-list.ts +205 -0
  147. package/src/ui/components/dropdown-menu.tsx +73 -0
  148. package/src/ui/components/empty-state.tsx +51 -0
  149. package/src/ui/components/highlight.ts +59 -0
  150. package/src/ui/components/icons.tsx +101 -0
  151. package/src/ui/components/image-posture.tsx +85 -0
  152. package/src/ui/components/input.tsx +28 -0
  153. package/src/ui/components/label.tsx +16 -0
  154. package/src/ui/components/line-chart.tsx +113 -0
  155. package/src/ui/components/page.tsx +39 -0
  156. package/src/ui/components/permission-row.tsx +262 -0
  157. package/src/ui/components/permission-section.tsx +51 -0
  158. package/src/ui/components/pie-chart.tsx +245 -0
  159. package/src/ui/components/schema-block.tsx +60 -0
  160. package/src/ui/components/separator.tsx +42 -0
  161. package/src/ui/components/sheet.tsx +130 -0
  162. package/src/ui/components/sidebar.tsx +650 -0
  163. package/src/ui/components/silo-picker.tsx +510 -0
  164. package/src/ui/components/skeleton.tsx +13 -0
  165. package/src/ui/components/sonner.tsx +33 -0
  166. package/src/ui/components/switch.tsx +58 -0
  167. package/src/ui/components/table-view.ts +219 -0
  168. package/src/ui/components/table.tsx +506 -0
  169. package/src/ui/components/tabs.tsx +59 -0
  170. package/src/ui/components/timeago.tsx +84 -0
  171. package/src/ui/components/tooltip.tsx +56 -0
  172. package/src/ui/components.json +21 -0
  173. package/src/ui/dev/dev-shell.tsx +145 -0
  174. package/src/ui/home/home-grid.tsx +177 -0
  175. package/src/ui/hooks/use-mobile.ts +19 -0
  176. package/src/ui/index.ts +189 -0
  177. package/src/ui/install/adapter.ts +193 -0
  178. package/src/ui/install/deploy-targets-picker.tsx +185 -0
  179. package/src/ui/install/install-app-panel.tsx +912 -0
  180. package/src/ui/install/install-error.tsx +62 -0
  181. package/src/ui/install/perms-inactive.ts +50 -0
  182. package/src/ui/install/required-fields.ts +37 -0
  183. package/src/ui/install/tabs-form.tsx +557 -0
  184. package/src/ui/tokens.css +279 -0
  185. package/src/ui/utils.ts +78 -0
  186. package/src/ui-error.ts +139 -0
  187. package/src/ui-runtime/dev-landing-entry.tsx +258 -0
  188. package/src/ui-runtime/index.ts +83 -0
  189. package/src/ui-runtime/install-entry.tsx +119 -0
  190. package/src/ui-runtime/mount.ts +367 -0
  191. package/src/ui-runtime/runtime.css +56 -0
  192. package/src/ui-surface.ts +451 -0
  193. package/src/valkey.ts +43 -0
  194. package/src/worker/extract-tar.ts +125 -0
  195. package/src/worker/identity.ts +183 -0
  196. package/src/worker/index.ts +16 -0
  197. package/src/worker/public-ip.ts +92 -0
  198. package/src/worker/queue.ts +54 -0
  199. package/src/worker/reconcile.ts +127 -0
  200. package/src/worker/start-worker.ts +227 -0
  201. package/src/worker/ui-proxy.ts +335 -0
  202. package/src/worker/uplink.ts +523 -0
  203. package/src/worker/validate.ts +34 -0
  204. package/src/worker/verify-relay.ts +102 -0
  205. package/src/worker-protocol/app-dirs.ts +94 -0
  206. package/src/worker-protocol/home-proxy-paths.ts +76 -0
  207. package/src/worker-protocol/index.ts +55 -0
  208. package/src/worker-protocol/ingress.ts +166 -0
  209. package/src/worker-protocol/logs-socket-path.ts +50 -0
  210. package/src/worker-protocol/networks.ts +39 -0
  211. package/src/worker-protocol/node-version.ts +70 -0
  212. package/src/worker-protocol/silo-paths.ts +114 -0
  213. package/src/worker-protocol/spawn-paths.ts +165 -0
  214. package/src/worker-protocol/stats-socket-path.ts +40 -0
  215. package/src/worker-protocol/update-files.ts +77 -0
  216. package/src/worker-protocol/uplink/constants.ts +86 -0
  217. package/src/worker-protocol/uplink/errors.ts +61 -0
  218. package/src/worker-protocol/uplink/frame.ts +114 -0
  219. package/src/worker-protocol/uplink/identity.ts +40 -0
  220. package/src/worker-protocol/uplink/index.ts +63 -0
  221. package/src/worker-protocol/uplink/methods.ts +462 -0
  222. package/src/worker-protocol/uplink/mux.ts +646 -0
  223. package/src/worker-protocol/uplink/protocol.ts +11 -0
  224. package/src/worker-protocol/uplink/rpc.ts +309 -0
  225. package/src/worker-protocol/uplink/session.ts +540 -0
  226. package/src/worker-protocol/uplink/test-support.ts +188 -0
  227. package/src/worker-protocol/worker-mount.ts +27 -0
  228. package/src/worker-protocol/worker-state.ts +190 -0
  229. package/src/xml.ts +49 -0
  230. package/src/yaml.ts +15 -0
@@ -0,0 +1,56 @@
1
+ import type { ComponentChildren, ComponentProps } from "preact";
2
+ import { Tooltip as TooltipPrimitive } from "@ark-ui/react/tooltip";
3
+
4
+ import { cn } from "../utils.ts";
5
+
6
+ function TooltipProvider({ children }: { children: ComponentChildren }) {
7
+ return <>{children}</>;
8
+ }
9
+
10
+ function Tooltip({ openDelay = 0, ...props }: ComponentProps<typeof TooltipPrimitive.Root>) {
11
+ return <TooltipPrimitive.Root openDelay={openDelay} {...props} />;
12
+ }
13
+
14
+ function TooltipTrigger({ ...props }: ComponentProps<typeof TooltipPrimitive.Trigger>) {
15
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
16
+ }
17
+
18
+ function TooltipContent({
19
+ className,
20
+ side = "top",
21
+ sideOffset = 4,
22
+ align = "center",
23
+ alignOffset = 0,
24
+ children,
25
+ ...props
26
+ }: ComponentProps<typeof TooltipPrimitive.Content> & {
27
+ side?: "top" | "right" | "bottom" | "left";
28
+ sideOffset?: number;
29
+ align?: "start" | "center" | "end";
30
+ alignOffset?: number;
31
+ }) {
32
+ const placement =
33
+ align === "center" ? side : (`${side}-${align === "start" ? "start" : "end"}` as const);
34
+ return (
35
+ <TooltipPrimitive.Positioner
36
+ className="isolate z-50"
37
+ positioning={{ placement, offset: { mainAxis: sideOffset, crossAxis: alignOffset } }}
38
+ >
39
+ <TooltipPrimitive.Content
40
+ data-slot="tooltip-content"
41
+ className={cn(
42
+ "z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
43
+ className,
44
+ )}
45
+ {...props}
46
+ >
47
+ {children}
48
+ <TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground">
49
+ <TooltipPrimitive.ArrowTip />
50
+ </TooltipPrimitive.Arrow>
51
+ </TooltipPrimitive.Content>
52
+ </TooltipPrimitive.Positioner>
53
+ );
54
+ }
55
+
56
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "base-nova",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "tokens.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "aliases": {
15
+ "components": "@yourrealm/sdk/ui",
16
+ "utils": "@yourrealm/sdk/ui/utils",
17
+ "ui": "@yourrealm/sdk/ui/components",
18
+ "lib": "@yourrealm/sdk/ui",
19
+ "hooks": "@yourrealm/sdk/ui/hooks"
20
+ }
21
+ }
@@ -0,0 +1,145 @@
1
+ // Shell chrome shared by `realm-sdk dev`'s entry points
2
+ // (dev-landing-entry.tsx, install-entry.tsx), so the chrome matches a real Home
3
+ // install bit-for-bit. Wraps a Home-style sidebar (the same `Sidebar*`
4
+ // primitives Home's DashboardShell uses) around the page-specific content:
5
+ //
6
+ // - Navigation: Home, Install
7
+ // - Pages: every declared `ui.pages` entry (sidebar label + icon comes from
8
+ // the resolved UI manifest)
9
+ //
10
+ // Widgets are not in the sidebar — they appear on Home alongside router tiles,
11
+ // mirroring Home's `/` layout.
12
+
13
+ import type { ComponentChildren, JSX } from "preact";
14
+ import {
15
+ Sidebar,
16
+ SidebarContent,
17
+ SidebarFooter,
18
+ SidebarGroup,
19
+ SidebarGroupLabel,
20
+ SidebarHeader,
21
+ SidebarInset,
22
+ SidebarMenu,
23
+ SidebarMenuButton,
24
+ SidebarMenuItem,
25
+ SidebarProvider,
26
+ SidebarTrigger,
27
+ } from "../components/sidebar.tsx";
28
+ import { DownloadSimpleIcon, HouseIcon, resolveIcon } from "../components/icons.tsx";
29
+
30
+ export interface PageEntry {
31
+ key: string;
32
+ title: string;
33
+ icon?: string;
34
+ }
35
+
36
+ export interface DevShellState {
37
+ appName: string;
38
+ pages: PageEntry[];
39
+ }
40
+
41
+ interface NavItem {
42
+ href: string;
43
+ label: string;
44
+ Icon: (props: { className?: string }) => JSX.Element;
45
+ }
46
+
47
+ const NAV: NavItem[] = [
48
+ { href: "/", label: "Home", Icon: HouseIcon },
49
+ { href: "/install", label: "Install", Icon: DownloadSimpleIcon },
50
+ ];
51
+
52
+ interface DevShellProps {
53
+ state: DevShellState;
54
+ pathname: string;
55
+ sidebarOpen?: boolean;
56
+ /** Breadcrumb / page title shown in the top bar. */
57
+ title: string;
58
+ /** Icon (by name) shown next to `title`. */
59
+ titleIcon?: string;
60
+ children: ComponentChildren;
61
+ }
62
+
63
+ export function DevShell({
64
+ state,
65
+ pathname,
66
+ sidebarOpen = true,
67
+ title,
68
+ titleIcon,
69
+ children,
70
+ }: DevShellProps) {
71
+ const TitleIcon = resolveIcon(titleIcon);
72
+ return (
73
+ <SidebarProvider defaultOpen={sidebarOpen}>
74
+ <Sidebar collapsible="icon">
75
+ <SidebarHeader>
76
+ <div class="px-2 py-1 text-sm font-bold tracking-tight group-data-[collapsible=icon]:hidden">
77
+ {state.appName}
78
+ </div>
79
+ </SidebarHeader>
80
+ <SidebarContent>
81
+ <SidebarGroup>
82
+ <SidebarGroupLabel>Navigation</SidebarGroupLabel>
83
+ <SidebarMenu>
84
+ {NAV.map(({ href, label, Icon }) => (
85
+ <SidebarMenuItem key={href}>
86
+ <SidebarMenuButton asChild tooltip={label} isActive={pathname === href}>
87
+ <a href={href}>
88
+ <Icon />
89
+ <span class="flex-1">{label}</span>
90
+ </a>
91
+ </SidebarMenuButton>
92
+ </SidebarMenuItem>
93
+ ))}
94
+ </SidebarMenu>
95
+ </SidebarGroup>
96
+ {state.pages.length > 0 && (
97
+ <SidebarGroup>
98
+ <SidebarGroupLabel>Pages</SidebarGroupLabel>
99
+ <SidebarMenu>
100
+ {state.pages.map((p) => {
101
+ const href = `/pages/${encodeURIComponent(p.key)}`;
102
+ const Icon = resolveIcon(p.icon);
103
+ return (
104
+ <SidebarMenuItem key={p.key}>
105
+ <SidebarMenuButton asChild tooltip={p.title} isActive={pathname === href}>
106
+ <a href={href}>
107
+ <Icon />
108
+ <span class="flex-1 truncate">{p.title}</span>
109
+ </a>
110
+ </SidebarMenuButton>
111
+ </SidebarMenuItem>
112
+ );
113
+ })}
114
+ </SidebarMenu>
115
+ </SidebarGroup>
116
+ )}
117
+ </SidebarContent>
118
+ <SidebarFooter>
119
+ <SidebarMenu>
120
+ <SidebarMenuItem>
121
+ <div class="flex items-center gap-2 px-2 py-1 text-xs text-sidebar-foreground/70 group-data-[collapsible=icon]:hidden">
122
+ <span class="truncate font-mono font-semibold text-sidebar-foreground">
123
+ realm-sdk dev
124
+ </span>
125
+ </div>
126
+ </SidebarMenuItem>
127
+ </SidebarMenu>
128
+ </SidebarFooter>
129
+ </Sidebar>
130
+ <SidebarInset>
131
+ <div class="flex h-12 shrink-0 items-center gap-3 border-b border-border px-3">
132
+ <SidebarTrigger />
133
+ <nav
134
+ aria-label="Breadcrumb"
135
+ class="flex items-center gap-2 text-sm font-medium text-foreground min-w-0"
136
+ >
137
+ <TitleIcon className="size-4 text-muted-foreground" />
138
+ <span class="truncate">{title}</span>
139
+ </nav>
140
+ </div>
141
+ <div class="flex min-h-0 flex-1 flex-col">{children}</div>
142
+ </SidebarInset>
143
+ </SidebarProvider>
144
+ );
145
+ }
@@ -0,0 +1,177 @@
1
+ // Mixed router-tile + widget-iframe grid used by Home's home page and
2
+ // `realm-sdk dev`'s landing page. Both render the same chrome (8rem
3
+ // square app icons + 2×2 widget iframes in an auto-fill grid), they
4
+ // just point at different iframe URLs.
5
+ //
6
+ // Home call site: platform/home/src/routes/(authed)/index.tsx
7
+ // Dev call site: packages/sdk/src/ui-runtime/dev-landing-entry.tsx
8
+
9
+ import type { ComponentChildren, JSX } from "preact";
10
+ import type { WidgetManifest } from "../../resolve.ts";
11
+ import { CubeIcon, resolveIcon } from "../components/icons.tsx";
12
+
13
+ export interface RouterTile {
14
+ /** Stable preact key. Home uses `${appId}/${serviceId}`, dev uses `serviceId`. */
15
+ key: string;
16
+ label: string;
17
+ /** Anchor target. Omit to render a non-clickable tile. */
18
+ href?: string;
19
+ /** Open in a new tab. Only meaningful when `href` is set. */
20
+ external?: boolean;
21
+ /** Icon URL. Home serves its own `/api/icons/<hash>`; dev uses the source URL. */
22
+ iconSrc?: string;
23
+ /** Optional `data-testid` for the anchor; Home uses `home-app-<appId>-<serviceId>`. */
24
+ testId?: string;
25
+ }
26
+
27
+ /**
28
+ * Tile shape for one homepage widget. `title`/`icon`/`cols`/`rows`
29
+ * come straight from the wire-side `WidgetManifest` (Home's
30
+ * `extractUiSurfaces` projects them, dev's landing payload mirrors
31
+ * them); `key` and `iframeSrc` are produced by the renderer that
32
+ * decided where this tile mounts.
33
+ *
34
+ * Below `sm` the grid collapses to two full-width cells: `cols` clamps
35
+ * to 2 and `rows` grows via `ceil(cols * rows / min(cols, 2))` so the
36
+ * widget keeps its area. `rows === 1` widgets skip the title-bar
37
+ * chrome — author owns labeling inside the iframe.
38
+ */
39
+ export type WidgetTile = Pick<WidgetManifest, "title" | "icon" | "cols" | "rows"> & {
40
+ /** Stable preact key. Home uses `${appId}/${widgetKey}`, dev uses `widgetKey`. */
41
+ key: string;
42
+ /** Full iframe `src` — Home: `/api/ui/<appId>/widget/<key>`, dev: `/iframe/widget/<key>`. */
43
+ iframeSrc: string;
44
+ /** Whether the widget declares a non-empty `configSchema` (drives the tile's
45
+ * "configure" affordance). Omitted where a caller doesn't track it. */
46
+ hasConfig?: boolean;
47
+ };
48
+
49
+ interface HomeGridProps {
50
+ routers: RouterTile[];
51
+ widgets: WidgetTile[];
52
+ /**
53
+ * Bump nonce. Appended to each widget iframe's `key` so the iframe
54
+ * remounts when the value changes. Dev mode uses this to reload
55
+ * widgets on realm.ts edits; Home leaves it at 0.
56
+ */
57
+ reloadNonce?: number;
58
+ /** Rendered when both `routers` and `widgets` are empty. */
59
+ emptyState: ComponentChildren;
60
+ /**
61
+ * Whether to apply the grid's own `p-6`. True (default) for full-bleed use
62
+ * (the home page, dev landing). Set false when embedding inside a page that
63
+ * already pads its body, so the grid doesn't sit doubly inset.
64
+ */
65
+ padded?: boolean;
66
+ }
67
+
68
+ // Responsive grid + widget sizing, expressed with Tailwind arbitrary-value
69
+ // utilities so the rules land in the bundled stylesheet - an injected <style>
70
+ // element doesn't survive island hydration when this renders as slotted
71
+ // children of Home's DashboardShell island, and silently falls back to
72
+ // display:block.
73
+ //
74
+ // Desktop (≥ sm): fixed 8rem cells in an auto-fill grid; each widget spans its
75
+ // declared `--cols × --rows`. Mobile (< sm): two stretched columns, with
76
+ // `--mobile-cols` / `--mobile-rows` preserving the widget's area. Cell height
77
+ // stays 8rem so a `rows=2` widget stays 16rem tall.
78
+ const GRID_CLASS =
79
+ "grid gap-4 grid-cols-[repeat(auto-fill,8rem)] auto-rows-[8rem] max-sm:grid-cols-2";
80
+ const WIDGET_SPAN_CLASS =
81
+ "[grid-column:span_var(--cols)] [grid-row:span_var(--rows)] " +
82
+ "max-sm:[grid-column:span_var(--mobile-cols)] max-sm:[grid-row:span_var(--mobile-rows)]";
83
+
84
+ interface WidgetSize {
85
+ cols: number;
86
+ rows: number;
87
+ mobileCols: number;
88
+ mobileRows: number;
89
+ }
90
+
91
+ function widgetSize(tile: WidgetTile): WidgetSize {
92
+ const mobileCols = Math.min(tile.cols, 2);
93
+ // Preserve area: a 3×2 widget becomes 2×3 on mobile, a 4×2 becomes 2×4.
94
+ const mobileRows = Math.ceil((tile.cols * tile.rows) / mobileCols);
95
+ return { cols: tile.cols, rows: tile.rows, mobileCols, mobileRows };
96
+ }
97
+
98
+ export function HomeGrid({
99
+ routers,
100
+ widgets,
101
+ reloadNonce = 0,
102
+ emptyState,
103
+ padded = true,
104
+ }: HomeGridProps): JSX.Element {
105
+ const isEmpty = routers.length === 0 && widgets.length === 0;
106
+ return (
107
+ <div class={padded ? "flex flex-col gap-6 p-6" : "flex flex-col gap-6"}>
108
+ {isEmpty ? (
109
+ emptyState
110
+ ) : (
111
+ <div class={GRID_CLASS}>
112
+ {routers.map((r) => {
113
+ const anchorProps = r.href
114
+ ? {
115
+ href: r.href,
116
+ ...(r.external ? { target: "_blank", rel: "noreferrer" } : {}),
117
+ }
118
+ : {};
119
+ const Tag = r.href ? "a" : "div";
120
+ return (
121
+ <Tag
122
+ key={r.key}
123
+ {...anchorProps}
124
+ {...(r.testId ? { "data-testid": r.testId } : {})}
125
+ class="group flex flex-col items-center justify-center gap-2.5 p-2 rounded-2xl"
126
+ >
127
+ <div class="size-20 rounded-3xl bg-gradient-to-br from-card to-purple-light shadow-clay flex items-center justify-center transition-transform duration-200 group-hover:-translate-y-1 dark:shadow-clay-dark">
128
+ {r.iconSrc ? (
129
+ <img src={r.iconSrc} alt="" class="size-12" loading="lazy" />
130
+ ) : (
131
+ <CubeIcon className="size-10 text-primary/70" />
132
+ )}
133
+ </div>
134
+ <span class="text-xs font-medium text-center w-full truncate">{r.label}</span>
135
+ </Tag>
136
+ );
137
+ })}
138
+ {widgets.map((w) => {
139
+ const Icon = resolveIcon(w.icon);
140
+ const size = widgetSize(w);
141
+ // `rows === 1` widgets are too short for a title bar; the
142
+ // author owns labeling inside the iframe. Gate on the
143
+ // declared rows so the chrome stays off even when mobile
144
+ // collapse reflows a `cols=3, rows=1` strip into 2×2.
145
+ const showChrome = w.rows > 1;
146
+ return (
147
+ <div
148
+ key={w.key}
149
+ class={`${WIDGET_SPAN_CLASS} rounded-3xl bg-card shadow-clay overflow-hidden flex flex-col dark:shadow-clay-dark`}
150
+ style={{
151
+ "--cols": size.cols,
152
+ "--rows": size.rows,
153
+ "--mobile-cols": size.mobileCols,
154
+ "--mobile-rows": size.mobileRows,
155
+ }}
156
+ >
157
+ {showChrome && (
158
+ <div class="flex items-center gap-2 px-3 py-2 text-sm border-b border-border">
159
+ <Icon className="size-4 text-muted-foreground" />
160
+ <span class="truncate font-medium">{w.title}</span>
161
+ </div>
162
+ )}
163
+ <iframe
164
+ key={`${w.key}:${reloadNonce}`}
165
+ src={w.iframeSrc}
166
+ sandbox="allow-scripts allow-forms"
167
+ class="w-full flex-1 border-0 bg-card"
168
+ title={w.title}
169
+ />
170
+ </div>
171
+ );
172
+ })}
173
+ </div>
174
+ )}
175
+ </div>
176
+ );
177
+ }
@@ -0,0 +1,19 @@
1
+ import { useEffect, useState } from "preact/hooks";
2
+
3
+ const MOBILE_BREAKPOINT = 768;
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = useState<boolean | undefined>(undefined);
7
+
8
+ useEffect(() => {
9
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
10
+ const onChange = () => {
11
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
12
+ };
13
+ mql.addEventListener("change", onChange);
14
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
15
+ return () => mql.removeEventListener("change", onChange);
16
+ }, []);
17
+
18
+ return !!isMobile;
19
+ }
@@ -0,0 +1,189 @@
1
+ // Render functions declared on `ui.pages.*` / `ui.widgets.*` are extracted by
2
+ // realm-sdk and bundled with `@yourrealm/sdk/ui` externalized. The actual
3
+ // component implementations are served by Home as the iframe runtime; this
4
+ // module is a type-only contract with stubs that throw if invoked outside the
5
+ // iframe.
6
+ //
7
+ // The `loader`, `action`, `page`, and `widget` helpers are real (identity at
8
+ // runtime) because they exist purely for TS inference — see ui-surface.ts. The
9
+ // matching types (`Page<T>`, `Widget<T>`) aren't re-exported here to avoid
10
+ // colliding with the `Page` layout component below; authors use the helpers
11
+ // and never need to spell the types out.
12
+
13
+ export { action, loader, page, widget } from "../ui-surface.ts";
14
+ // Typed loader/action failures — the codes a surface (or the runtime's
15
+ // initial-load gate) branches on instead of substring-matching a message.
16
+ export { isTransientUiError, isUiError, UiError, type UiErrorCode } from "../ui-error.ts";
17
+ // `Props<L, A>` is the type of a default-exported surface render fn when a
18
+ // surface lives in its own `pages/*.tsx` / `widgets/*.tsx` module. Unlike
19
+ // `Page<T>` / `Widget<T>` it has no name clash, so it's safe to export.
20
+ export type {
21
+ Action,
22
+ IconName,
23
+ Loader,
24
+ Props,
25
+ SurfaceConfig,
26
+ SurfaceConfigSchema,
27
+ WorkerResult,
28
+ } from "../ui-surface.ts";
29
+
30
+ import type * as _badge from "./components/badge.tsx";
31
+ import type * as _button from "./components/button.tsx";
32
+ import type * as _card from "./components/card.tsx";
33
+ import type * as _checkbox from "./components/checkbox.tsx";
34
+ import type * as _combobox from "./components/combobox.tsx";
35
+ import type * as _dialog from "./components/dialog.tsx";
36
+ import type * as _emptyState from "./components/empty-state.tsx";
37
+ import type * as _icons from "./components/icons.tsx";
38
+ import type * as _input from "./components/input.tsx";
39
+ import type * as _label from "./components/label.tsx";
40
+ import type * as _lineChart from "./components/line-chart.tsx";
41
+ import type * as _page from "./components/page.tsx";
42
+ import type * as _pieChart from "./components/pie-chart.tsx";
43
+ import type * as _separator from "./components/separator.tsx";
44
+ import type * as _sheet from "./components/sheet.tsx";
45
+ import type * as _skeleton from "./components/skeleton.tsx";
46
+ import type * as _switch from "./components/switch.tsx";
47
+ import type * as _table from "./components/table.tsx";
48
+ import type * as _timeago from "./components/timeago.tsx";
49
+ import type * as _tooltip from "./components/tooltip.tsx";
50
+ import type * as _preact from "preact";
51
+
52
+ export type { ComponentChildren, JSX, VNode } from "preact";
53
+
54
+ const stub = ((): never => {
55
+ throw new Error(
56
+ "@yourrealm/sdk/ui has no runtime in this environment. Render functions are bundled by realm-sdk and execute inside the Home iframe.",
57
+ );
58
+ // deno-lint-ignore no-explicit-any
59
+ }) as any;
60
+
61
+ export const h: typeof _preact.h = stub;
62
+ export const Fragment: typeof _preact.Fragment = stub;
63
+
64
+ export const Badge: typeof _badge.Badge = stub;
65
+
66
+ export const Button: typeof _button.Button = stub;
67
+ export const buttonVariants: typeof _button.buttonVariants = stub;
68
+
69
+ export const Card: typeof _card.Card = stub;
70
+ export const CardAction: typeof _card.CardAction = stub;
71
+ export const CardContent: typeof _card.CardContent = stub;
72
+ export const CardDescription: typeof _card.CardDescription = stub;
73
+ export const CardFooter: typeof _card.CardFooter = stub;
74
+ export const CardHeader: typeof _card.CardHeader = stub;
75
+ export const CardTitle: typeof _card.CardTitle = stub;
76
+
77
+ export const Checkbox: typeof _checkbox.Checkbox = stub;
78
+
79
+ export const Combobox: typeof _combobox.Combobox = stub;
80
+
81
+ export const EmptyState: typeof _emptyState.EmptyState = stub;
82
+
83
+ export const Dialog: typeof _dialog.Dialog = stub;
84
+ export const DialogClose: typeof _dialog.DialogClose = stub;
85
+ export const DialogContent: typeof _dialog.DialogContent = stub;
86
+ export const DialogDescription: typeof _dialog.DialogDescription = stub;
87
+ export const DialogFooter: typeof _dialog.DialogFooter = stub;
88
+ export const DialogHeader: typeof _dialog.DialogHeader = stub;
89
+ export const DialogOverlay: typeof _dialog.DialogOverlay = stub;
90
+ export const DialogTitle: typeof _dialog.DialogTitle = stub;
91
+ export const DialogTrigger: typeof _dialog.DialogTrigger = stub;
92
+
93
+ export const Input: typeof _input.Input = stub;
94
+
95
+ export const Label: typeof _label.Label = stub;
96
+
97
+ export const LineChart: typeof _lineChart.LineChart = stub;
98
+ export const AreaChart: typeof _lineChart.AreaChart = stub;
99
+
100
+ export const Page: typeof _page.Page = stub;
101
+
102
+ export const PieChart: typeof _pieChart.PieChart = stub;
103
+ export type { PieSegment } from "./components/pie-chart.tsx";
104
+
105
+ export const Separator: typeof _separator.Separator = stub;
106
+
107
+ export const Sheet: typeof _sheet.Sheet = stub;
108
+ export const SheetClose: typeof _sheet.SheetClose = stub;
109
+ export const SheetContent: typeof _sheet.SheetContent = stub;
110
+ export const SheetDescription: typeof _sheet.SheetDescription = stub;
111
+ export const SheetFooter: typeof _sheet.SheetFooter = stub;
112
+ export const SheetHeader: typeof _sheet.SheetHeader = stub;
113
+ export const SheetTitle: typeof _sheet.SheetTitle = stub;
114
+ export const SheetTrigger: typeof _sheet.SheetTrigger = stub;
115
+
116
+ export const Skeleton: typeof _skeleton.Skeleton = stub;
117
+
118
+ export const Switch: typeof _switch.Switch = stub;
119
+
120
+ export const Table: typeof _table.Table = stub;
121
+ export const TableBody: typeof _table.TableBody = stub;
122
+ export const TableCell: typeof _table.TableCell = stub;
123
+ export const TableHead: typeof _table.TableHead = stub;
124
+ export const TableHeader: typeof _table.TableHeader = stub;
125
+ export const TableRow: typeof _table.TableRow = stub;
126
+ export const TablePagination: typeof _table.TablePagination = stub;
127
+ // The generic signature survives the assignment to `stub`, so `K` still infers
128
+ // from the `columns` literal at the call site.
129
+ export const useTableView: typeof _table.useTableView = stub;
130
+ // `export type`, not a stub: `verbatimModuleSyntax` is on, and these are the
131
+ // shapes an author's accessors and sort state are written against.
132
+ export type { SortDirection, SortState, SortValue } from "./components/table-view.ts";
133
+
134
+ export const TimeAgo: typeof _timeago.TimeAgo = stub;
135
+ export const timeago: typeof _timeago.timeago = stub;
136
+
137
+ export const Tooltip: typeof _tooltip.Tooltip = stub;
138
+ export const TooltipContent: typeof _tooltip.TooltipContent = stub;
139
+ export const TooltipProvider: typeof _tooltip.TooltipProvider = stub;
140
+ export const TooltipTrigger: typeof _tooltip.TooltipTrigger = stub;
141
+
142
+ export const ArrowsClockwiseIcon: typeof _icons.ArrowsClockwiseIcon = stub;
143
+ export const ArrowSquareOutIcon: typeof _icons.ArrowSquareOutIcon = stub;
144
+ export const CheckBoldIcon: typeof _icons.CheckBoldIcon = stub;
145
+ export const CheckCircleIcon: typeof _icons.CheckCircleIcon = stub;
146
+ export const CheckIcon: typeof _icons.CheckIcon = stub;
147
+ export const ClipboardIcon: typeof _icons.ClipboardIcon = stub;
148
+ export const ClockIcon: typeof _icons.ClockIcon = stub;
149
+ export const CloudArrowDownIcon: typeof _icons.CloudArrowDownIcon = stub;
150
+ export const CubeIcon: typeof _icons.CubeIcon = stub;
151
+ export const DownloadSimpleIcon: typeof _icons.DownloadSimpleIcon = stub;
152
+ export const EnvelopeIcon: typeof _icons.EnvelopeIcon = stub;
153
+ export const EyeIcon: typeof _icons.EyeIcon = stub;
154
+ export const FadersIcon: typeof _icons.FadersIcon = stub;
155
+ export const FolderOpenIcon: typeof _icons.FolderOpenIcon = stub;
156
+ export const FolderSimpleIcon: typeof _icons.FolderSimpleIcon = stub;
157
+ export const GearIcon: typeof _icons.GearIcon = stub;
158
+ export const GiftIcon: typeof _icons.GiftIcon = stub;
159
+ export const GithubLogoIcon: typeof _icons.GithubLogoIcon = stub;
160
+ export const GlobeIcon: typeof _icons.GlobeIcon = stub;
161
+ export const GraphicsCardIcon: typeof _icons.GraphicsCardIcon = stub;
162
+ export const HandshakeIcon: typeof _icons.HandshakeIcon = stub;
163
+ export const HardDrivesIcon: typeof _icons.HardDrivesIcon = stub;
164
+ export const HouseIcon: typeof _icons.HouseIcon = stub;
165
+ export const InfoIcon: typeof _icons.InfoIcon = stub;
166
+ export const KeyIcon: typeof _icons.KeyIcon = stub;
167
+ export const LightbulbIcon: typeof _icons.LightbulbIcon = stub;
168
+ export const ListChecksIcon: typeof _icons.ListChecksIcon = stub;
169
+ export const LockIcon: typeof _icons.LockIcon = stub;
170
+ export const MagnifyingGlassIcon: typeof _icons.MagnifyingGlassIcon = stub;
171
+ export const PackageIcon: typeof _icons.PackageIcon = stub;
172
+ export const PlugIcon: typeof _icons.PlugIcon = stub;
173
+ export const PlusIcon: typeof _icons.PlusIcon = stub;
174
+ export const PulseIcon: typeof _icons.PulseIcon = stub;
175
+ export const QuestionIcon: typeof _icons.QuestionIcon = stub;
176
+ export const RocketLaunchIcon: typeof _icons.RocketLaunchIcon = stub;
177
+ export const ShareNetworkIcon: typeof _icons.ShareNetworkIcon = stub;
178
+ export const ShieldIcon: typeof _icons.ShieldIcon = stub;
179
+ export const SidebarIcon: typeof _icons.SidebarIcon = stub;
180
+ export const SignOutIcon: typeof _icons.SignOutIcon = stub;
181
+ export const StackIcon: typeof _icons.StackIcon = stub;
182
+ export const StarIcon: typeof _icons.StarIcon = stub;
183
+ export const TerminalWindowIcon: typeof _icons.TerminalWindowIcon = stub;
184
+ export const TrashIcon: typeof _icons.TrashIcon = stub;
185
+ export const UserIcon: typeof _icons.UserIcon = stub;
186
+ export const WarningIcon: typeof _icons.WarningIcon = stub;
187
+ export const WarningOctagonIcon: typeof _icons.WarningOctagonIcon = stub;
188
+ export const XBoldIcon: typeof _icons.XBoldIcon = stub;
189
+ export const XIcon: typeof _icons.XIcon = stub;