@k2b/cloud 0.6.0 → 0.7.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 (309) hide show
  1. package/README.md +2 -2
  2. package/package.json +19 -9
  3. package/scripts/browser-performance.ts +14 -0
  4. package/scripts/build-pdf-renderer.ts +39 -0
  5. package/scripts/build.ts +15 -35
  6. package/scripts/preload.ts +9 -12
  7. package/scripts/sync-recovery-smoke.ts +2 -1
  8. package/src/_internal/app-presentation.ts +14 -2
  9. package/src/_internal/capabilities.ts +108 -7
  10. package/src/_internal/capability-streams.ts +81 -0
  11. package/src/_internal/define-app.ts +62 -36
  12. package/src/_internal/heartbeat.ts +4 -0
  13. package/src/_internal/help-catalog.ts +27 -172
  14. package/src/_internal/help.ts +13 -76
  15. package/src/_internal/page-responses.ts +2 -0
  16. package/src/_internal/postgres-application-name.ts +30 -0
  17. package/src/_internal/process-identity.ts +15 -0
  18. package/src/_internal/process-sync.ts +1 -1
  19. package/src/_internal/registry-validation.ts +29 -25
  20. package/src/_internal/registry.ts +2 -53
  21. package/src/_internal/runtime-context.ts +5 -16
  22. package/src/_internal/server-timing.ts +13 -0
  23. package/src/_internal/status-preserving-ssr.ts +18 -6
  24. package/src/_internal/web-vitals-asset.ts +10 -0
  25. package/src/access/PermissionEditor.tsx +14 -153
  26. package/src/access/PrincipalPicker.tsx +104 -0
  27. package/src/access/ui.ts +2 -0
  28. package/src/ai/admin.ts +14 -13
  29. package/src/ai/assistant-models.ts +12 -1
  30. package/src/ai/audio-format.ts +34 -0
  31. package/src/ai/audio-tool.ts +90 -0
  32. package/src/ai/browser-code-contracts.ts +79 -0
  33. package/src/ai/browser.ts +2 -0
  34. package/src/ai/capabilities.ts +55 -36
  35. package/src/ai/capability-execution.ts +47 -12
  36. package/src/ai/chat/blocks.tsx +157 -95
  37. package/src/ai/chat/capability-result.ts +15 -0
  38. package/src/ai/chat/capability-table.tsx +2 -10
  39. package/src/ai/chat/composer-adapter.ts +48 -3
  40. package/src/ai/chat/message-actions.tsx +8 -7
  41. package/src/ai/chat/message-utils.ts +12 -1
  42. package/src/ai/chat/messages.ts +2 -0
  43. package/src/ai/chat/presentation.tsx +19 -48
  44. package/src/ai/chat/primitives.tsx +43 -1
  45. package/src/ai/chat/tool-disclosure.tsx +17 -2
  46. package/src/ai/chat/tool-groups.ts +52 -0
  47. package/src/ai/chat-quotas.ts +25 -0
  48. package/src/ai/chat-task-contracts.ts +9 -1
  49. package/src/ai/chat-tasks.ts +276 -43
  50. package/src/ai/client/controller.ts +110 -33
  51. package/src/ai/client/file-source.ts +12 -6
  52. package/src/ai/client/live-connection.ts +1 -0
  53. package/src/ai/client/projection.ts +22 -11
  54. package/src/ai/client/transport.ts +4 -0
  55. package/src/ai/code-capability-routes.ts +110 -0
  56. package/src/ai/code-capability-transport.ts +38 -0
  57. package/src/ai/code-mode-skill.ts +114 -0
  58. package/src/ai/code-runtime-tools.ts +166 -0
  59. package/src/ai/code-source-contracts.ts +202 -0
  60. package/src/ai/code-source-tools.ts +145 -0
  61. package/src/ai/compaction.ts +27 -6
  62. package/src/ai/data-analysis-skill.ts +10 -0
  63. package/src/ai/default-tools.ts +25 -2
  64. package/src/ai/dictation-runtime.ts +171 -0
  65. package/src/ai/dictations.ts +188 -0
  66. package/src/ai/draft-content.ts +34 -0
  67. package/src/ai/enrich.ts +6 -0
  68. package/src/ai/executor.ts +191 -31
  69. package/src/ai/file-content-version.ts +22 -0
  70. package/src/ai/file-context.ts +2 -1
  71. package/src/ai/file-media-type.ts +12 -0
  72. package/src/ai/file-reference-contracts.ts +15 -0
  73. package/src/ai/file-tools.ts +2 -1
  74. package/src/ai/files-store.ts +174 -71
  75. package/src/ai/fixtures/vision-encrypted.pdf +0 -0
  76. package/src/ai/grids-skill.ts +40 -10
  77. package/src/ai/http.ts +15 -8
  78. package/src/ai/index.ts +26 -2
  79. package/src/ai/inference-calls.ts +198 -0
  80. package/src/ai/live-events.ts +5 -1
  81. package/src/ai/memory-learning.ts +60 -28
  82. package/src/ai/message-queue.ts +167 -0
  83. package/src/ai/migrate.ts +187 -11
  84. package/src/ai/model-pricing.ts +43 -0
  85. package/src/ai/pdf-render-worker.ts +74 -0
  86. package/src/ai/pdf-render.fixture.ts +28 -0
  87. package/src/ai/pdf-render.ts +77 -0
  88. package/src/ai/projects-routes.ts +3 -3
  89. package/src/ai/projects.ts +20 -6
  90. package/src/ai/protocol.ts +3 -0
  91. package/src/ai/provider.ts +1 -8
  92. package/src/ai/quota-provider.ts +191 -0
  93. package/src/ai/quota-report.ts +152 -0
  94. package/src/ai/quotas-migrate.ts +45 -0
  95. package/src/ai/quotas.ts +168 -0
  96. package/src/ai/routes.ts +215 -11
  97. package/src/ai/run-timeout.ts +10 -0
  98. package/src/ai/runtime-tools.ts +3 -0
  99. package/src/ai/runtime.ts +55 -16
  100. package/src/ai/settings.ts +36 -8
  101. package/src/ai/short-id.ts +2 -2
  102. package/src/ai/skill-catalog.ts +0 -10
  103. package/src/ai/skill-search.ts +47 -0
  104. package/src/ai/skill-seeds.ts +105 -20
  105. package/src/ai/skill-tool.ts +16 -5
  106. package/src/ai/skills-routes.ts +32 -4
  107. package/src/ai/skills.ts +324 -155
  108. package/src/ai/store.ts +373 -191
  109. package/src/ai/stream.ts +5 -0
  110. package/src/ai/structured-runs.ts +4 -3
  111. package/src/ai/structured.ts +8 -1
  112. package/src/ai/system-prompt.ts +12 -2
  113. package/src/ai/timeline.ts +2 -5
  114. package/src/ai/todo-contracts.ts +20 -0
  115. package/src/ai/todo-tool.ts +16 -0
  116. package/src/ai/tool-audit.ts +30 -40
  117. package/src/ai/tools.ts +6 -0
  118. package/src/ai/transcription.ts +202 -0
  119. package/src/ai/turn-timing.ts +114 -0
  120. package/src/ai/types.ts +58 -10
  121. package/src/ai/ui.tsx +2 -0
  122. package/src/ai/usage.ts +118 -118
  123. package/src/ai/vision-tool.ts +39 -10
  124. package/src/api/admin-ai-projects.ts +2 -2
  125. package/src/api/admin-ai-quotas.ts +74 -0
  126. package/src/api/admin-ai-skills.ts +48 -5
  127. package/src/api/admin-ai-usage.ts +2 -2
  128. package/src/api/admin-core-settings.ts +25 -0
  129. package/src/api/admin-identity.ts +91 -51
  130. package/src/api/admin-rail.ts +58 -0
  131. package/src/api/announcements.ts +14 -0
  132. package/src/api/capabilities.ts +292 -43
  133. package/src/api/capability-streams.ts +262 -0
  134. package/src/api/help.ts +8 -44
  135. package/src/api/index.ts +6 -0
  136. package/src/api/mcp.ts +44 -26
  137. package/src/api/me-web-vitals.ts +35 -0
  138. package/src/api/me.ts +2 -0
  139. package/src/api/search/schemas.ts +26 -15
  140. package/src/api/search.bench.ts +1 -1
  141. package/src/api/search.ts +54 -17
  142. package/src/browser/CloudResourceSearch.tsx +640 -702
  143. package/src/browser/app-approval-vault.ts +19 -7
  144. package/src/browser/command-bridge.ts +109 -0
  145. package/src/browser/command-shortcuts.ts +76 -0
  146. package/src/browser/commands.ts +66 -0
  147. package/src/browser/navigation-search.ts +48 -0
  148. package/src/browser/notifications.ts +1 -1
  149. package/src/browser/resource-picker.tsx +5 -18
  150. package/src/browser/resource-search-dialog.ts +9 -0
  151. package/src/browser/resource-search-input.ts +46 -0
  152. package/src/browser/resource-search-messages.ts +76 -40
  153. package/src/browser/resource-search.ts +7 -0
  154. package/src/browser/search-bridge.ts +102 -0
  155. package/src/browser/search-commands.ts +59 -0
  156. package/src/browser/search.ts +13 -0
  157. package/src/browser/spotlight-position.ts +208 -0
  158. package/src/browser/testing.ts +5 -0
  159. package/src/browser/web-vitals.ts +25 -0
  160. package/src/capabilities/claims.ts +179 -0
  161. package/src/capabilities/client.ts +34 -0
  162. package/src/capabilities/command-link.ts +16 -0
  163. package/src/capabilities/executions.ts +298 -0
  164. package/src/capabilities/migrate.ts +81 -0
  165. package/src/capabilities/server.ts +114 -34
  166. package/src/capabilities/stream-body.ts +16 -0
  167. package/src/capabilities/streams.ts +28 -0
  168. package/src/cli/admin/ai-quotas.ts +161 -0
  169. package/src/cli/admin/ai-skills.ts +64 -0
  170. package/src/cli/admin/ai-usage.ts +53 -30
  171. package/src/cli/admin/app-credentials.ts +79 -0
  172. package/src/cli/admin/data.ts +17 -0
  173. package/src/cli/admin/index.ts +9 -0
  174. package/src/cli/capabilities.ts +29 -0
  175. package/src/cli/capability-streams.ts +25 -0
  176. package/src/cli/index.ts +3 -2
  177. package/src/config/env.ts +11 -8
  178. package/src/contracts/app.ts +23 -3
  179. package/src/contracts/capabilities.ts +68 -5
  180. package/src/contracts/capability-streams.ts +46 -0
  181. package/src/contracts/commands.ts +49 -0
  182. package/src/contracts/index.ts +6 -1
  183. package/src/contracts/rail-admin.ts +38 -0
  184. package/src/contracts/rail-preferences.ts +16 -0
  185. package/src/contracts/registry.ts +3 -27
  186. package/src/contracts/settings-types.ts +1 -1
  187. package/src/contracts/shared.ts +9 -5
  188. package/src/contracts/web-vitals.ts +27 -0
  189. package/src/index.ts +0 -4
  190. package/src/server/index.ts +3 -0
  191. package/src/server/middleware/auth.ts +3 -2
  192. package/src/server/middleware/route-template.ts +9 -3
  193. package/src/server/middleware/runtime.ts +2 -1
  194. package/src/server/middleware/settings.ts +17 -11
  195. package/src/server/services/access-revision.ts +10 -0
  196. package/src/server/services/access.ts +1 -1
  197. package/src/server/services/index.ts +1 -0
  198. package/src/services/account-category-policy.ts +9 -0
  199. package/src/services/account-lifecycle/audit.ts +1 -1
  200. package/src/services/accounts/app.ts +12 -2
  201. package/src/services/accounts/email-write.ts +23 -0
  202. package/src/services/accounts/groups.ts +15 -4
  203. package/src/services/accounts/identities.ts +249 -0
  204. package/src/services/accounts/identity-reconciliation.ts +87 -0
  205. package/src/services/accounts/ipa-data.ts +2 -2
  206. package/src/services/accounts/local-groups.ts +2 -2
  207. package/src/services/accounts/posix.ts +45 -29
  208. package/src/services/accounts/users.ts +2 -2
  209. package/src/services/announcements/index.ts +69 -8
  210. package/src/services/audit/index.ts +1 -1
  211. package/src/services/cache-fill.ts +35 -0
  212. package/src/services/help/index.ts +109 -0
  213. package/src/services/help/maintenance.ts +25 -0
  214. package/src/services/help/store.ts +180 -0
  215. package/src/services/help/types.ts +46 -0
  216. package/src/services/identity/invocation-actor.ts +2 -2
  217. package/src/services/identity/key-config.ts +2 -1
  218. package/src/services/identity/key-ring.ts +1 -1
  219. package/src/services/index.ts +15 -0
  220. package/src/services/ipa/users.ts +268 -221
  221. package/src/services/legal-consent.ts +1 -1
  222. package/src/services/logging/index.ts +15 -13
  223. package/src/services/logging/trace.ts +2 -1
  224. package/src/services/logging/web-vitals.ts +81 -0
  225. package/src/services/mandates/index.ts +18 -4
  226. package/src/services/mandates/policy.ts +65 -2
  227. package/src/services/notifications/batches.ts +124 -57
  228. package/src/services/notifications/browser.ts +12 -0
  229. package/src/services/notifications/index.ts +0 -2
  230. package/src/services/notifications/platform.ts +0 -4
  231. package/src/services/oauth-tokens.ts +3 -3
  232. package/src/services/pdf/gotenberg.ts +107 -84
  233. package/src/services/pdf/index.ts +3 -0
  234. package/src/services/providers/local/users.ts +25 -16
  235. package/src/services/public-http.ts +101 -0
  236. package/src/services/rail-shortcuts.ts +95 -0
  237. package/src/services/rail-snapshot.ts +53 -0
  238. package/src/services/request-cache-redis.ts +28 -0
  239. package/src/services/session/index.ts +15 -2
  240. package/src/services/session/user.ts +62 -37
  241. package/src/services/settings/core-settings.ts +21 -28
  242. package/src/services/settings/defaults.ts +5 -0
  243. package/src/services/settings/store.ts +58 -84
  244. package/src/shared/ai-costs.ts +42 -0
  245. package/src/shared/ai-platform-prompt.ts +1 -1
  246. package/src/shared/ai-quotas.ts +136 -0
  247. package/src/shared/ai-usage.ts +6 -16
  248. package/src/shared/app-presentation.ts +21 -15
  249. package/src/shared/capability-messages.ts +14 -0
  250. package/src/shared/help.ts +6 -42
  251. package/src/shared/index.ts +6 -0
  252. package/src/ssr/AdminLayout.tsx +7 -5
  253. package/src/ssr/AdminSidebar.tsx +29 -15
  254. package/src/ssr/AppLaunchpad.island.tsx +24 -137
  255. package/src/ssr/AppLaunchpadPanel.tsx +181 -0
  256. package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
  257. package/src/ssr/GlobalSearchDialog.tsx +167 -20
  258. package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
  259. package/src/ssr/Layout.tsx +17 -6
  260. package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
  261. package/src/ssr/LayoutFooter.tsx +1 -1
  262. package/src/ssr/LayoutHeader.tsx +46 -14
  263. package/src/ssr/LayoutHelp.tsx +23 -22
  264. package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
  265. package/src/ssr/LayoutRail.tsx +17 -9
  266. package/src/ssr/MobileNavigation.tsx +71 -0
  267. package/src/ssr/MobileProfileActions.tsx +49 -0
  268. package/src/ssr/ProfilePreferences.island.tsx +3 -36
  269. package/src/ssr/RailApps.island.tsx +2 -2
  270. package/src/ssr/RailEditor.tsx +17 -2
  271. package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
  272. package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
  273. package/src/ssr/admin-navigation.ts +7 -1
  274. package/src/ssr/help.ts +29 -0
  275. package/src/ssr/islands/SearchBar.tsx +67 -0
  276. package/src/ssr/islands/index.ts +3 -1
  277. package/src/ssr/layout-context.ts +2 -2
  278. package/src/ssr/layout-help-search.ts +15 -0
  279. package/src/ssr/mobile-menu-history.ts +70 -0
  280. package/src/ssr/platform-messages.ts +16 -8
  281. package/src/ssr/profile-actions.ts +42 -0
  282. package/src/ssr/rail-context.ts +8 -4
  283. package/src/ssr/rail-messages.ts +2 -0
  284. package/src/ssr/rail-navigation.ts +3 -3
  285. package/src/ssr/workspace-navigation.ts +75 -0
  286. package/src/styles/effects.css +6 -6
  287. package/src/styles/global.css +54 -0
  288. package/src/styles/input.css +43 -41
  289. package/src/styles/resource-search.css +505 -0
  290. package/src/styles/utilities-feedback.css +13 -0
  291. package/src/styles/utilities-layout.css +1 -5
  292. package/src/styles/utilities-navigation.css +3 -39
  293. package/src/types/ambient.d.ts +5 -0
  294. package/src/workflows/ai/runtime.ts +5 -1
  295. package/src/workflows/ai/store.ts +2 -0
  296. package/src/workflows/ai-actions.ts +16 -13
  297. package/src/workflows/definition.ts +10 -5
  298. package/src/workflows/store/actions.ts +44 -20
  299. package/src/workflows/store/index.ts +2 -0
  300. package/src/workflows/store/runs.ts +36 -0
  301. package/src/workflows/store/worker-pool.ts +55 -0
  302. package/src/workflows/store/worker-runtime.ts +90 -0
  303. package/src/ai/kit-skill.ts +0 -22
  304. package/src/ai/usage-migrate.ts +0 -81
  305. package/src/browser/notification-target.ts +0 -27
  306. package/src/contracts/notification-live.ts +0 -66
  307. package/src/services/notifications/live.ts +0 -50
  308. package/src/ssr/BrowserNotifications.island.tsx +0 -135
  309. package/src/ssr/islands/SearchBar.island.tsx +0 -80
@@ -1,66 +0,0 @@
1
- import { z } from "zod";
2
- import { isSafeNotificationTargetHref } from "./notification-types";
3
-
4
- export const NOTIFICATION_LIVE_WS_TYPE = {
5
- subscribe: "notifications.live.subscribe",
6
- ready: "notifications.live.ready",
7
- event: "notifications.live.event",
8
- revoked: "notifications.live.revoked",
9
- error: "notifications.live.error",
10
- } as const;
11
-
12
- export const NotificationStreamCursorSchema = z
13
- .string()
14
- .regex(/^s6t\.[A-Za-z0-9_-]+\.\d+$/)
15
- .max(80);
16
-
17
- const NotificationTargetHrefSchema = z.custom<`/${string}`>(
18
- (value) => typeof value === "string" && isSafeNotificationTargetHref(value),
19
- "Notification target must be a canonical same-origin path",
20
- );
21
-
22
- export const NotificationLiveEventSchema = z.object({
23
- type: z.literal("cloud-notification"),
24
- eventId: z.string().min(1),
25
- title: z.string(),
26
- targetHref: NotificationTargetHrefSchema.optional(),
27
- });
28
-
29
- export type NotificationLiveEvent = z.infer<typeof NotificationLiveEventSchema>;
30
-
31
- export const NotificationLiveClientMessageSchema = z.object({
32
- type: z.literal(NOTIFICATION_LIVE_WS_TYPE.subscribe),
33
- payload: z.object({ fromCursor: NotificationStreamCursorSchema.nullable() }),
34
- });
35
-
36
- export type NotificationLiveClientMessage = z.infer<typeof NotificationLiveClientMessageSchema>;
37
-
38
- export const NotificationLiveServerMessageSchema = z.discriminatedUnion("type", [
39
- z.object({
40
- type: z.literal(NOTIFICATION_LIVE_WS_TYPE.ready),
41
- payload: z.object({ cursor: NotificationStreamCursorSchema }),
42
- }),
43
- z.object({
44
- type: z.literal(NOTIFICATION_LIVE_WS_TYPE.event),
45
- payload: z.object({ cursor: NotificationStreamCursorSchema, event: NotificationLiveEventSchema }),
46
- }),
47
- z.object({
48
- type: z.literal(NOTIFICATION_LIVE_WS_TYPE.revoked),
49
- payload: z.object({ code: z.string().min(1), message: z.string().min(1) }),
50
- }),
51
- z.object({
52
- type: z.literal(NOTIFICATION_LIVE_WS_TYPE.error),
53
- payload: z.object({ code: z.string().min(1), message: z.string().min(1) }),
54
- }),
55
- ]);
56
-
57
- export type NotificationLiveServerMessage = z.infer<typeof NotificationLiveServerMessageSchema>;
58
-
59
- export const parseNotificationLiveServerMessage = (raw: string): NotificationLiveServerMessage | null => {
60
- try {
61
- const parsed = NotificationLiveServerMessageSchema.safeParse(JSON.parse(raw));
62
- return parsed.success ? parsed.data : null;
63
- } catch {
64
- return null;
65
- }
66
- };
@@ -1,50 +0,0 @@
1
- import { lazySync } from "../../_internal/process-sync";
2
- import type { NotificationLiveEvent } from "../../contracts/notification-live";
3
- import type { NotificationPresentation } from "../../contracts/notification-types";
4
- import { logger } from "../logging";
5
- import { latestTopicCursor } from "../topic-cursor";
6
-
7
- const LIVE_TOPIC_ID = "cloud:notifications:live";
8
- const LIVE_RETENTION_MS = 60 * 60 * 1_000;
9
-
10
- const log = logger("notifications:live");
11
-
12
- const liveTopic = lazySync((sync) =>
13
- sync.topic<NotificationLiveEvent>({
14
- id: LIVE_TOPIC_ID,
15
- owner: "core",
16
- // One hour of foreground alerts fits the shared event-log storage budget.
17
- retention: { maxAgeMs: LIVE_RETENTION_MS, maxBytes: 64 * 1024 * 1024 },
18
- maxPayloadBytes: 9_000,
19
- }),
20
- );
21
-
22
- const publish = async (input: { userId: string; eventId: string; presentation: NotificationPresentation }): Promise<void> => {
23
- const event: NotificationLiveEvent = {
24
- type: "cloud-notification",
25
- eventId: input.eventId,
26
- title: input.presentation.title,
27
- ...(input.presentation.targetHref ? { targetHref: input.presentation.targetHref } : {}),
28
- };
29
- try {
30
- await liveTopic().publish({
31
- tenantId: input.userId,
32
- orderingKey: input.userId,
33
- idempotencyKey: `event:${input.eventId}`,
34
- data: event,
35
- });
36
- } catch (error) {
37
- log.warn("Failed to publish foreground notification", {
38
- eventId: input.eventId,
39
- error: error instanceof Error ? error.message : "Foreground notification publish failed",
40
- });
41
- }
42
- };
43
-
44
- export const notificationLive = {
45
- publish,
46
- latestCursor: (userId: string): Promise<string> => latestTopicCursor({ topic: liveTopic(), resourceId: LIVE_TOPIC_ID, tenantId: userId }),
47
- emptyCursor: (): string => liveTopic().cursorAt(0),
48
- events: (input: { userId: string; after?: string; signal?: AbortSignal }) =>
49
- liveTopic().hub({ tenantId: input.userId }).subscribe({ after: input.after, signal: input.signal }),
50
- } as const;
@@ -1,135 +0,0 @@
1
- import { IconButton, useLocale } from "@k2b/ui";
2
- import { createSignal, onCleanup, onMount, Show } from "solid-js";
3
- import { createLiveWebSocket } from "../browser/live-websocket";
4
- import { notificationTargetMatchesLocation } from "../browser/notification-target";
5
- import { browserNotificationClient } from "../browser/notifications";
6
- import {
7
- NOTIFICATION_LIVE_WS_TYPE,
8
- type NotificationLiveClientMessage,
9
- type NotificationLiveEvent,
10
- NotificationLiveEventSchema,
11
- type NotificationLiveServerMessage,
12
- NotificationStreamCursorSchema,
13
- parseNotificationLiveServerMessage,
14
- } from "../contracts/notification-live";
15
- import { platformMessages } from "./platform-messages";
16
-
17
- const cursorStorageKey = (userId: string): string => `cloud.notifications.live-cursor:${userId}`;
18
-
19
- const readStoredCursor = (userId: string): string | null => {
20
- try {
21
- const cursor = sessionStorage.getItem(cursorStorageKey(userId));
22
- return NotificationStreamCursorSchema.safeParse(cursor).success ? cursor : null;
23
- } catch {
24
- return null;
25
- }
26
- };
27
-
28
- const storeCursor = (userId: string, cursor: string) => {
29
- try {
30
- sessionStorage.setItem(cursorStorageKey(userId), cursor);
31
- } catch {
32
- // Private or restricted browsing may disable storage; in-memory resume still works.
33
- }
34
- };
35
-
36
- export default function BrowserNotifications(props: { userId: string }) {
37
- const locale = useLocale();
38
- const t = () => platformMessages.resolve([locale()]).t;
39
- const [notification, setNotification] = createSignal<NotificationLiveEvent | null>(null);
40
- const seen = new Set<string>();
41
-
42
- onMount(() => {
43
- const receive = (value: NotificationLiveEvent) => {
44
- if (seen.has(value.eventId)) return true;
45
- seen.add(value.eventId);
46
- if (seen.size > 500) seen.delete(seen.values().next().value ?? "");
47
- if (value.targetHref && notificationTargetMatchesLocation(value.targetHref, window.location.href)) return true;
48
- setNotification(value);
49
- return true;
50
- };
51
-
52
- void browserNotificationClient.refreshExisting().catch((error) => {
53
- console.warn("[notifications] Failed to refresh the browser endpoint", error);
54
- });
55
-
56
- const onMessage = (event: MessageEvent<unknown>) => {
57
- const parsed = NotificationLiveEventSchema.safeParse(event.data);
58
- if (!parsed.success) return;
59
- receive(parsed.data);
60
- event.ports[0]?.postMessage({ received: true });
61
- };
62
- navigator.serviceWorker?.addEventListener("message", onMessage);
63
-
64
- const initialCursor = readStoredCursor(props.userId);
65
- const connection = createLiveWebSocket<NotificationLiveServerMessage>({
66
- url: "/api/me/notifications/ws",
67
- initialCursor,
68
- activity: "visible",
69
- subscribe: (cursor) =>
70
- ({
71
- type: NOTIFICATION_LIVE_WS_TYPE.subscribe,
72
- payload: { fromCursor: cursor },
73
- }) satisfies NotificationLiveClientMessage,
74
- parse: parseNotificationLiveServerMessage,
75
- onMessage: (message, controls) => {
76
- if (message.type === NOTIFICATION_LIVE_WS_TYPE.ready) {
77
- controls.markApplied(message.payload.cursor);
78
- storeCursor(props.userId, message.payload.cursor);
79
- return;
80
- }
81
- if (message.type === NOTIFICATION_LIVE_WS_TYPE.event) {
82
- receive(message.payload.event);
83
- controls.markApplied(message.payload.cursor);
84
- storeCursor(props.userId, message.payload.cursor);
85
- return;
86
- }
87
- if (message.type === NOTIFICATION_LIVE_WS_TYPE.revoked) {
88
- controls.terminate({ code: message.payload.code, message: message.payload.message });
89
- }
90
- },
91
- onFatal: (error) => {
92
- if (error.code === "login_required" || error.code === "access_denied") window.location.reload();
93
- else console.warn("[notifications] Foreground updates stopped", error);
94
- },
95
- });
96
- connection.connect();
97
-
98
- onCleanup(() => {
99
- navigator.serviceWorker?.removeEventListener("message", onMessage);
100
- connection.dispose();
101
- });
102
- });
103
-
104
- return (
105
- <Show when={notification()} keyed>
106
- {(item) => (
107
- <aside
108
- class="paper fixed bottom-3 left-3 right-3 z-40 flex items-start gap-3 p-3 shadow-lg sm:left-auto sm:w-96"
109
- aria-live="polite"
110
- aria-label={t().newNotification}
111
- >
112
- <span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-blue-500/10 text-blue-600 dark:bg-blue-400/15 dark:text-blue-400">
113
- <i class="ti ti-bell" />
114
- </span>
115
- <div class="min-w-0 flex-1">
116
- <p class="truncate text-sm font-medium text-primary">{item.title}</p>
117
- <p class="mt-0.5 text-xs text-dimmed">{t().relatedItemReady}</p>
118
- {item.targetHref && (
119
- <a
120
- href={item.targetHref}
121
- class="mt-2 inline-flex items-center gap-1 text-xs font-medium text-blue-600 hover:underline dark:text-blue-400"
122
- >
123
- {t().open}
124
- <i class="ti ti-arrow-right" />
125
- </a>
126
- )}
127
- </div>
128
- <IconButton size="xs" class="shrink-0" label={t().dismissNotification} onClick={() => setNotification(null)}>
129
- <i class="ti ti-x" />
130
- </IconButton>
131
- </aside>
132
- )}
133
- </Show>
134
- );
135
- }
@@ -1,80 +0,0 @@
1
- import { TextInput, useLocale } from "@k2b/ui";
2
- import { createEffect, createSignal, onMount } from "solid-js";
3
- import { platformMessages } from "../platform-messages";
4
-
5
- type SearchBarProps = {
6
- action?: string;
7
- value?: string;
8
- param?: string;
9
- pageParam?: string;
10
- placeholder?: string;
11
- ariaLabel?: string;
12
- };
13
-
14
- /** Search bar that filters content via URL query parameter. */
15
- export default function SearchBar(props: SearchBarProps = {}) {
16
- const locale = useLocale();
17
- const t = () => platformMessages.resolve([locale()]).t;
18
- const param = props.param ?? "search";
19
- const pageParam = props.pageParam ?? "page";
20
- const [query, setQuery] = createSignal(props.value ?? "");
21
-
22
- createEffect(() => {
23
- if (props.value !== undefined) {
24
- setQuery(props.value);
25
- }
26
- });
27
-
28
- onMount(() => {
29
- if (props.value !== undefined) return;
30
- const fallback = new URLSearchParams(window.location.search).get(param) ?? "";
31
- setQuery(fallback);
32
- });
33
-
34
- const handleSubmit = (e: Event): void => {
35
- e.preventDefault();
36
- const current = new URL(window.location.href);
37
- const url = props.action ? new URL(props.action, window.location.origin) : current;
38
- const value = query().trim();
39
-
40
- if (value.length > 0) {
41
- url.searchParams.set(param, value);
42
- } else {
43
- url.searchParams.delete(param);
44
- }
45
- url.searchParams.delete(pageParam);
46
-
47
- window.location.href = url.toString();
48
- };
49
-
50
- const handleClear = (): void => {
51
- const current = new URL(window.location.href);
52
- const url = props.action ? new URL(props.action, window.location.origin) : current;
53
-
54
- url.searchParams.delete(param);
55
- url.searchParams.delete(pageParam);
56
-
57
- window.location.href = url.toString();
58
- };
59
-
60
- return (
61
- <form onSubmit={handleSubmit} role="search" class="w-full">
62
- <TextInput
63
- name={param}
64
- type="search"
65
- placeholder={props.placeholder ?? `${t().search}…`}
66
- aria-label={props.ariaLabel ?? t().search}
67
- icon="ti ti-search"
68
- activeIcon="ti ti-search"
69
- value={query}
70
- onValueChange={setQuery}
71
- clearable
72
- clearLabel={t().clearSearch}
73
- onClear={handleClear}
74
- />
75
- <button type="submit" class="hidden">
76
- {t().search}
77
- </button>
78
- </form>
79
- );
80
- }