@protobase/ui 0.0.0-stage → 0.1.1

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 (232) hide show
  1. package/LICENSE +202 -0
  2. package/dist/activity-chart/activity-chart.js +125 -0
  3. package/dist/activity-chart/activity-math.js +11 -0
  4. package/dist/activity-chart/activity-tooltip.js +24 -0
  5. package/dist/app/action-icon.js +10 -0
  6. package/dist/app/app.d.ts +24 -0
  7. package/dist/app/app.js +123 -0
  8. package/dist/app/batch-failure.js +21 -0
  9. package/dist/app/breadcrumb-for.js +27 -0
  10. package/dist/app/create/create-page.js +118 -0
  11. package/dist/app/create/create-record-form.js +68 -0
  12. package/dist/app/create/create-values.js +52 -0
  13. package/dist/app/delete/dialogs.js +109 -0
  14. package/dist/app/delete/record-diff.js +17 -0
  15. package/dist/app/delete/use-delete-flow.js +193 -0
  16. package/dist/app/delete/use-restore.js +9 -0
  17. package/dist/app/draft-storage.js +18 -0
  18. package/dist/app/error-banner.js +28 -0
  19. package/dist/app/field-editor.js +88 -0
  20. package/dist/app/icons.js +27 -0
  21. package/dist/app/index.d.ts +2 -0
  22. package/dist/app/index.html +12 -0
  23. package/dist/app/list-chart.js +36 -0
  24. package/dist/app/list-order.js +15 -0
  25. package/dist/app/list-page.js +213 -0
  26. package/dist/app/list-search.js +30 -0
  27. package/dist/app/main.d.ts +1 -0
  28. package/dist/app/main.js +14 -0
  29. package/dist/app/menu-icons.js +21 -0
  30. package/dist/app/meta-gate.js +46 -0
  31. package/dist/app/nav-from-meta.js +65 -0
  32. package/dist/app/nav-recent.js +33 -0
  33. package/dist/app/pages/action-href.js +7 -0
  34. package/dist/app/pages/blocks/action.js +38 -0
  35. package/dist/app/pages/blocks/block-props.js +5 -0
  36. package/dist/app/pages/blocks/card-frame.js +34 -0
  37. package/dist/app/pages/blocks/card-row.js +55 -0
  38. package/dist/app/pages/blocks/card.js +19 -0
  39. package/dist/app/pages/blocks/field.js +45 -0
  40. package/dist/app/pages/blocks/grid.js +34 -0
  41. package/dist/app/pages/blocks/link.js +27 -0
  42. package/dist/app/pages/blocks/modal-form.js +149 -0
  43. package/dist/app/pages/blocks/page-block.js +20 -0
  44. package/dist/app/pages/blocks/progress.js +54 -0
  45. package/dist/app/pages/blocks/record-card.js +56 -0
  46. package/dist/app/pages/blocks/show.js +43 -0
  47. package/dist/app/pages/blocks/stat.js +78 -0
  48. package/dist/app/pages/blocks/table.js +65 -0
  49. package/dist/app/pages/composed-page.js +19 -0
  50. package/dist/app/pages/confirm-action.js +20 -0
  51. package/dist/app/pages/field-value.js +58 -0
  52. package/dist/app/pages/project-ui.d.ts +30 -0
  53. package/dist/app/pages/project-ui.js +13 -0
  54. package/dist/app/pages/record-actions.js +40 -0
  55. package/dist/app/pages/record-scope.d.ts +16 -0
  56. package/dist/app/pages/record-scope.js +12 -0
  57. package/dist/app/pages/record-table.js +110 -0
  58. package/dist/app/pages/render-node.js +84 -0
  59. package/dist/app/pages/use-layout-data.js +73 -0
  60. package/dist/app/pages/use-path-value.js +65 -0
  61. package/dist/app/pages/use-relation-labels.js +30 -0
  62. package/dist/app/pages/use-run-action.js +88 -0
  63. package/dist/app/permissions.js +15 -0
  64. package/dist/app/record-lines.js +39 -0
  65. package/dist/app/record-page.js +318 -0
  66. package/dist/app/related-section.js +126 -0
  67. package/dist/app/relation-picker.js +69 -0
  68. package/dist/app/router.js +155 -0
  69. package/dist/app/search/search-model.js +70 -0
  70. package/dist/app/search/use-global-search.js +68 -0
  71. package/dist/app/sensitive-field.js +65 -0
  72. package/dist/app/use-lines-draft.js +45 -0
  73. package/dist/app/use-lines.js +19 -0
  74. package/dist/app/use-live-save.js +54 -0
  75. package/dist/app/use-nav-recent.js +41 -0
  76. package/dist/app/use-record-editor.js +154 -0
  77. package/dist/app/use-related-records.js +31 -0
  78. package/dist/app/user-menu-from-meta.js +36 -0
  79. package/dist/app-shell/app-shell.d.ts +4 -0
  80. package/dist/app-shell/app-shell.js +57 -0
  81. package/dist/app-shell/breadcrumb.js +37 -0
  82. package/dist/app-shell/drawer.js +30 -0
  83. package/dist/app-shell/global-search.d.ts +1 -0
  84. package/dist/app-shell/global-search.js +145 -0
  85. package/dist/app-shell/index.d.ts +9 -0
  86. package/dist/app-shell/logo.js +46 -0
  87. package/dist/app-shell/nav-open-storage.js +10 -0
  88. package/dist/app-shell/nav.d.ts +2 -0
  89. package/dist/app-shell/nav.js +85 -0
  90. package/dist/app-shell/page-header.d.ts +1 -0
  91. package/dist/app-shell/page-header.js +17 -0
  92. package/dist/app-shell/profile-menu.d.ts +2 -0
  93. package/dist/app-shell/profile-menu.js +115 -0
  94. package/dist/app-shell/sidebar-group.js +94 -0
  95. package/dist/app-shell/sidebar-item.js +46 -0
  96. package/dist/app-shell/sidebar-mode.d.ts +4 -0
  97. package/dist/app-shell/sidebar-mode.js +13 -0
  98. package/dist/app-shell/sidebar.d.ts +2 -0
  99. package/dist/app-shell/sidebar.js +64 -0
  100. package/dist/app-shell/status-dot.d.ts +1 -0
  101. package/dist/app-shell/status-dot.js +20 -0
  102. package/dist/app-shell/top-bar.js +82 -0
  103. package/dist/assistant-dock/assistant-dock.d.ts +15 -0
  104. package/dist/assistant-dock/assistant-dock.js +81 -0
  105. package/dist/assistant-dock/chat-message.js +9 -0
  106. package/dist/assistant-dock/composer.js +39 -0
  107. package/dist/assistant-dock/dock-item.d.ts +4 -0
  108. package/dist/assistant-dock/dock-item.js +37 -0
  109. package/dist/assistant-dock/model.d.ts +84 -0
  110. package/dist/assistant-dock/model.js +26 -0
  111. package/dist/assistant-dock/plan-card.d.ts +13 -0
  112. package/dist/assistant-dock/plan-card.js +133 -0
  113. package/dist/assistant-dock/progress-card.js +18 -0
  114. package/dist/assistant-dock/query-card.js +52 -0
  115. package/dist/assistant-dock/queue-card.js +31 -0
  116. package/dist/assistant-dock/result-card.js +77 -0
  117. package/dist/assistant-panel/content.d.ts +7 -0
  118. package/dist/assistant-panel/step-list.js +30 -0
  119. package/dist/auth/auth-gate.js +70 -0
  120. package/dist/auth/auth-layout.js +29 -0
  121. package/dist/auth/auth-provider.js +71 -0
  122. package/dist/auth/first-run-page.js +51 -0
  123. package/dist/auth/forgot-password-form.js +75 -0
  124. package/dist/auth/forgot-password-page.js +35 -0
  125. package/dist/auth/reset-link.js +29 -0
  126. package/dist/auth/reset-password-form.js +88 -0
  127. package/dist/auth/reset-password-page.js +43 -0
  128. package/dist/auth/sign-in-form.js +90 -0
  129. package/dist/auth/sign-in-page.js +39 -0
  130. package/dist/auth/user-to-shell.js +18 -0
  131. package/dist/data/api-provider.d.ts +54 -0
  132. package/dist/data/api-provider.js +53 -0
  133. package/dist/data/meta-data.d.ts +14 -0
  134. package/dist/data/meta-data.js +9 -0
  135. package/dist/data/query-keys.js +95 -0
  136. package/dist/data/use-aggregates.d.ts +8 -0
  137. package/dist/data/use-aggregates.js +43 -0
  138. package/dist/data/use-list.d.ts +231 -0
  139. package/dist/data/use-list.js +31 -0
  140. package/dist/data/use-meta.d.ts +8 -0
  141. package/dist/data/use-meta.js +35 -0
  142. package/dist/data/use-record.d.ts +6 -0
  143. package/dist/data/use-record.js +16 -0
  144. package/dist/data/use-update-record.d.ts +15 -0
  145. package/dist/data/use-update-record.js +26 -0
  146. package/dist/data-grid/cells.js +85 -0
  147. package/dist/data-grid/column-spec.js +4 -0
  148. package/dist/data-grid/columns.js +16 -0
  149. package/dist/data-grid/data-grid.js +238 -0
  150. package/dist/data-grid/geometry.js +32 -0
  151. package/dist/data-grid/grid-footer.js +33 -0
  152. package/dist/data-grid/grid-header.js +69 -0
  153. package/dist/data-grid/grid-row.js +83 -0
  154. package/dist/data-grid/grid-template.js +12 -0
  155. package/dist/data-grid/header-menu.js +52 -0
  156. package/dist/data-grid/page-cache.js +27 -0
  157. package/dist/data-grid/pinned-storage.js +13 -0
  158. package/dist/data-grid/row-menu.js +88 -0
  159. package/dist/data-grid/use-grid-scroll.js +80 -0
  160. package/dist/data-grid/use-horizontal-scroll.js +38 -0
  161. package/dist/data-grid/use-paged-rows.js +50 -0
  162. package/dist/data-grid/use-pinned-columns.js +16 -0
  163. package/dist/filter-panel/active-filter-chips.js +34 -0
  164. package/dist/filter-panel/date-presets.js +20 -0
  165. package/dist/filter-panel/facet-group.js +48 -0
  166. package/dist/filter-panel/filter-config.js +18 -0
  167. package/dist/filter-panel/filter-menu.js +41 -0
  168. package/dist/filter-panel/filter-panel.js +112 -0
  169. package/dist/filter-panel/filter-section.js +11 -0
  170. package/dist/filter-panel/filter-state.js +63 -0
  171. package/dist/filter-panel/range-filter.js +54 -0
  172. package/dist/filter-panel/range-math.js +11 -0
  173. package/dist/filter-panel/range-slider.css +32 -0
  174. package/dist/filter-panel/toggle-row.js +13 -0
  175. package/dist/filter-panel/use-filter-state.js +15 -0
  176. package/dist/format/date.js +37 -0
  177. package/dist/format/money.js +15 -0
  178. package/dist/format/number.js +12 -0
  179. package/dist/index.d.ts +14 -0
  180. package/dist/index.js +11 -0
  181. package/dist/inline-child-table/columns.js +4 -0
  182. package/dist/inline-child-table/inline-child-table.js +120 -0
  183. package/dist/inline-child-table/line-menu.js +54 -0
  184. package/dist/inline-child-table/line-row.js +91 -0
  185. package/dist/inline-child-table/number-cell.js +25 -0
  186. package/dist/inline-child-table/reorder.js +10 -0
  187. package/dist/inline-child-table/totals-footer.js +42 -0
  188. package/dist/lib/cn.js +4 -0
  189. package/dist/lib/guarded.js +12 -0
  190. package/dist/lib/use-debounced.js +13 -0
  191. package/dist/lib/use-focus-trap.js +46 -0
  192. package/dist/lib/use-media-query.js +12 -0
  193. package/dist/live/columns-from-model.js +93 -0
  194. package/dist/live/display-value.js +24 -0
  195. package/dist/live/field-values.js +32 -0
  196. package/dist/live/filter-string.js +135 -0
  197. package/dist/live/grid-loader.js +63 -0
  198. package/dist/live/model-helpers.js +30 -0
  199. package/dist/live/naming.js +9 -0
  200. package/dist/live/relation-labels.js +53 -0
  201. package/dist/live/use-filter-config.js +88 -0
  202. package/dist/live/use-live-rows.js +45 -0
  203. package/dist/live/view-fields.js +35 -0
  204. package/dist/mocks/activity.js +24 -0
  205. package/dist/mocks/invoices.js +13 -0
  206. package/dist/mocks/products.js +167 -0
  207. package/dist/mocks/rng.js +20 -0
  208. package/dist/mocks/users.js +63 -0
  209. package/dist/primitives/avatar.js +22 -0
  210. package/dist/primitives/badge.d.ts +1 -0
  211. package/dist/primitives/badge.js +25 -0
  212. package/dist/primitives/button.js +23 -0
  213. package/dist/primitives/checkbox.js +21 -0
  214. package/dist/primitives/dialog.js +41 -0
  215. package/dist/primitives/input.js +16 -0
  216. package/dist/primitives/kbd.js +8 -0
  217. package/dist/primitives/segmented.js +14 -0
  218. package/dist/primitives/sheet.js +44 -0
  219. package/dist/primitives/skeleton.js +6 -0
  220. package/dist/primitives/spinner.js +10 -0
  221. package/dist/primitives/switch.js +14 -0
  222. package/dist/primitives/tooltip.js +13 -0
  223. package/dist/record-view/action-rail.js +28 -0
  224. package/dist/record-view/field.js +62 -0
  225. package/dist/record-view/record-header.js +50 -0
  226. package/dist/record-view/record-sidebar.js +58 -0
  227. package/dist/record-view/section.js +21 -0
  228. package/dist/styles.css +132 -0
  229. package/dist/toasts/toast-provider.js +69 -0
  230. package/dist/toasts/toast.js +45 -0
  231. package/package.json +45 -3
  232. package/README.md +0 -3
@@ -0,0 +1,133 @@
1
+ import { Badge } from "../primitives/badge.js";
2
+ import { Button } from "../primitives/button.js";
3
+ import { approval, approveLabel, formatCredits, waitingText } from "./model.js";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { AlertTriangle, GitBranch, ListChecks } from "lucide-react";
6
+ //#region src/assistant-dock/plan-card.tsx
7
+ const statusNote = {
8
+ awaiting: void 0,
9
+ approved: "Approved. The credits are held until the change is live.",
10
+ canceled: "Canceled. The 1-credit deposit was kept.",
11
+ superseded: "Replaced by a newer plan.",
12
+ dropped: "The repository changed, so this plan was dropped and the deposit refunded."
13
+ };
14
+ const PlanCard = ({ plan, balance, onApprove, onRequestChanges, onCancel, onUpdatePlan }) => {
15
+ const approve = approval(plan, balance);
16
+ const awaiting = plan.status === "awaiting";
17
+ const waiting = awaiting ? waitingText(plan.waiting) : void 0;
18
+ return /* @__PURE__ */ jsxs("section", {
19
+ "aria-label": `Plan, revision ${plan.revision}`,
20
+ className: "shrink-0 overflow-hidden rounded-lg border border-primary-border bg-background shadow-sm",
21
+ children: [/* @__PURE__ */ jsxs("header", {
22
+ className: "flex items-center gap-2 bg-primary-soft px-3 py-2",
23
+ children: [
24
+ /* @__PURE__ */ jsx(ListChecks, { className: "size-3.5 text-primary-text" }),
25
+ /* @__PURE__ */ jsxs("h3", {
26
+ className: "flex-1 text-[13px] font-semibold text-primary-text",
27
+ children: ["Plan", plan.revision > 1 && `, revision ${plan.revision}`]
28
+ }),
29
+ /* @__PURE__ */ jsxs(Badge, {
30
+ tone: "blue",
31
+ dot: false,
32
+ children: ["Size ", plan.size]
33
+ }),
34
+ /* @__PURE__ */ jsx("span", {
35
+ className: "text-xs font-medium text-primary-text",
36
+ children: formatCredits(plan.credits)
37
+ })
38
+ ]
39
+ }), /* @__PURE__ */ jsxs("div", {
40
+ className: "space-y-2.5 px-3 py-2.5",
41
+ children: [
42
+ /* @__PURE__ */ jsx("p", {
43
+ className: "text-[13px]",
44
+ children: plan.summary
45
+ }),
46
+ plan.steps.length > 0 && /* @__PURE__ */ jsx("ol", {
47
+ className: "list-decimal space-y-1 pl-5 text-xs text-muted-foreground",
48
+ children: plan.steps.map((step) => /* @__PURE__ */ jsx("li", { children: step.title }, step.title))
49
+ }),
50
+ plan.migrations.length > 0 && /* @__PURE__ */ jsxs("div", {
51
+ className: "space-y-1",
52
+ children: [/* @__PURE__ */ jsx("div", {
53
+ className: "text-xs font-semibold",
54
+ children: "Database changes"
55
+ }), /* @__PURE__ */ jsx("ul", {
56
+ className: "space-y-1",
57
+ children: plan.migrations.map((migration) => /* @__PURE__ */ jsxs("li", {
58
+ className: "flex items-start gap-2 text-xs",
59
+ children: [/* @__PURE__ */ jsx("span", {
60
+ className: "min-w-0 flex-1",
61
+ children: migration.description
62
+ }), migration.destructive && /* @__PURE__ */ jsx(Badge, {
63
+ tone: "red",
64
+ children: "Destructive"
65
+ })]
66
+ }, migration.description))
67
+ })]
68
+ }),
69
+ plan.risks.length > 0 && /* @__PURE__ */ jsx("ul", {
70
+ className: "space-y-1",
71
+ children: plan.risks.map((risk) => /* @__PURE__ */ jsxs("li", {
72
+ className: "flex items-start gap-2 text-xs text-warning-text",
73
+ children: [/* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 size-3 shrink-0" }), risk]
74
+ }, risk))
75
+ }),
76
+ awaiting && plan.outdated && /* @__PURE__ */ jsxs("p", {
77
+ role: "status",
78
+ className: "flex items-start gap-2 rounded-md bg-warning-soft px-2.5 py-2 text-xs text-warning-text",
79
+ children: [/* @__PURE__ */ jsx(GitBranch, { className: "mt-0.5 size-3 shrink-0" }), "The branch moved on GitHub since this plan was made. Approving runs it on the new code."]
80
+ }),
81
+ waiting && /* @__PURE__ */ jsxs("p", {
82
+ className: "text-xs text-muted-foreground",
83
+ children: [waiting, " Cancel frees the queue."]
84
+ }),
85
+ statusNote[plan.status] && /* @__PURE__ */ jsx("p", {
86
+ className: "text-xs text-muted-foreground",
87
+ children: statusNote[plan.status]
88
+ }),
89
+ awaiting && /* @__PURE__ */ jsxs("div", {
90
+ className: "space-y-2 pt-0.5",
91
+ children: [
92
+ /* @__PURE__ */ jsx(Button, {
93
+ variant: "primary",
94
+ className: "w-full",
95
+ disabled: !approve.enabled,
96
+ onClick: () => onApprove?.(plan.taskId),
97
+ children: approveLabel(plan)
98
+ }),
99
+ approve.reason && /* @__PURE__ */ jsx("p", {
100
+ className: "text-xs text-danger-text",
101
+ children: approve.reason
102
+ }),
103
+ /* @__PURE__ */ jsxs("div", {
104
+ className: "flex gap-2",
105
+ children: [
106
+ plan.outdated && /* @__PURE__ */ jsx(Button, {
107
+ size: "sm",
108
+ className: "flex-1",
109
+ onClick: () => onUpdatePlan?.(plan.taskId),
110
+ children: "Update plan"
111
+ }),
112
+ /* @__PURE__ */ jsx(Button, {
113
+ size: "sm",
114
+ className: "flex-1",
115
+ onClick: () => onRequestChanges?.(plan.taskId),
116
+ children: "Ask for changes"
117
+ }),
118
+ /* @__PURE__ */ jsx(Button, {
119
+ size: "sm",
120
+ variant: "ghost",
121
+ onClick: () => onCancel?.(plan.taskId),
122
+ children: "Cancel"
123
+ })
124
+ ]
125
+ })
126
+ ]
127
+ })
128
+ ]
129
+ })]
130
+ });
131
+ };
132
+ //#endregion
133
+ export { PlanCard };
@@ -0,0 +1,18 @@
1
+ import { StepList } from "../assistant-panel/step-list.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/assistant-dock/progress-card.tsx
4
+ const titles = {
5
+ planning: "Planning",
6
+ running: "Making the change"
7
+ };
8
+ /** What the planning run or the job is doing, step by step. */
9
+ const ProgressCard = ({ phase, steps }) => /* @__PURE__ */ jsxs("section", {
10
+ "aria-label": titles[phase],
11
+ className: "shrink-0 space-y-2 rounded-lg border border-border-strong bg-background px-3 py-2.5 shadow-sm",
12
+ children: [/* @__PURE__ */ jsx("h3", {
13
+ className: "text-xs font-semibold",
14
+ children: titles[phase]
15
+ }), /* @__PURE__ */ jsx(StepList, { steps })]
16
+ });
17
+ //#endregion
18
+ export { ProgressCard };
@@ -0,0 +1,52 @@
1
+ import { formatCell } from "./model.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Database } from "lucide-react";
4
+ //#region src/assistant-dock/query-card.tsx
5
+ /** A read-only query the chat ran, with its rows as a table. */
6
+ const QueryCard = ({ query }) => /* @__PURE__ */ jsxs("section", {
7
+ "aria-label": "Query result",
8
+ className: "shrink-0 overflow-hidden rounded-lg border border-border-strong bg-background shadow-sm",
9
+ children: [
10
+ /* @__PURE__ */ jsxs("header", {
11
+ className: "flex items-start gap-2 bg-surface px-3 py-2",
12
+ children: [/* @__PURE__ */ jsx(Database, { className: "mt-0.5 size-3.5 shrink-0 text-muted-foreground" }), /* @__PURE__ */ jsx("code", {
13
+ className: "min-w-0 flex-1 whitespace-pre-wrap break-words font-mono text-[11px] text-foreground",
14
+ children: query.sql
15
+ })]
16
+ }),
17
+ query.rows.length === 0 ? /* @__PURE__ */ jsx("p", {
18
+ className: "px-3 py-2 text-xs text-muted-foreground",
19
+ children: "No rows."
20
+ }) : /* @__PURE__ */ jsx("div", {
21
+ className: "max-h-64 overflow-auto",
22
+ children: /* @__PURE__ */ jsxs("table", {
23
+ className: "w-full border-collapse text-left text-[11px]",
24
+ children: [/* @__PURE__ */ jsx("thead", {
25
+ className: "sticky top-0 bg-background",
26
+ children: /* @__PURE__ */ jsx("tr", { children: query.columns.map((column) => /* @__PURE__ */ jsx("th", {
27
+ scope: "col",
28
+ className: "border-b border-border px-2 py-1 font-semibold whitespace-nowrap",
29
+ children: column
30
+ }, column)) })
31
+ }), /* @__PURE__ */ jsx("tbody", { children: query.rows.map((row, index) => /* @__PURE__ */ jsx("tr", {
32
+ className: "border-b border-border last:border-b-0",
33
+ children: row.map((value, column) => /* @__PURE__ */ jsx("td", {
34
+ className: value === null ? "px-2 py-1 whitespace-nowrap text-faint-foreground" : "px-2 py-1 whitespace-nowrap",
35
+ children: formatCell(value)
36
+ }, column))
37
+ }, index)) })]
38
+ })
39
+ }),
40
+ /* @__PURE__ */ jsxs("footer", {
41
+ className: "border-t border-border px-3 py-1.5 text-[11px] text-muted-foreground",
42
+ children: [
43
+ query.rows.length,
44
+ " ",
45
+ query.rows.length === 1 ? "row" : "rows",
46
+ query.truncated && ", cut off at the limit"
47
+ ]
48
+ })
49
+ ]
50
+ });
51
+ //#endregion
52
+ export { QueryCard };
@@ -0,0 +1,31 @@
1
+ import { Button } from "../primitives/button.js";
2
+ import { queueText } from "./model.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { Clock } from "lucide-react";
5
+ //#region src/assistant-dock/queue-card.tsx
6
+ /** A task waiting in the app's queue, with its deposit billed. */
7
+ const QueueCard = ({ taskId, request, ahead, waitingOnPlan, onCancel }) => /* @__PURE__ */ jsxs("section", {
8
+ "aria-label": "Queued change",
9
+ className: "flex shrink-0 items-start gap-2.5 rounded-lg border border-border-strong bg-background px-3 py-2.5 shadow-sm",
10
+ children: [
11
+ /* @__PURE__ */ jsx(Clock, { className: "mt-0.5 size-3.5 shrink-0 text-muted-foreground" }),
12
+ /* @__PURE__ */ jsxs("div", {
13
+ className: "min-w-0 flex-1 space-y-0.5",
14
+ children: [/* @__PURE__ */ jsx("div", {
15
+ className: "truncate text-[13px] font-medium",
16
+ children: request
17
+ }), /* @__PURE__ */ jsx("div", {
18
+ className: "text-xs text-muted-foreground",
19
+ children: queueText(ahead, waitingOnPlan)
20
+ })]
21
+ }),
22
+ /* @__PURE__ */ jsx(Button, {
23
+ variant: "ghost",
24
+ size: "sm",
25
+ onClick: () => onCancel?.(taskId),
26
+ children: "Cancel"
27
+ })
28
+ ]
29
+ });
30
+ //#endregion
31
+ export { QueueCard };
@@ -0,0 +1,77 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import { formatCredits } from "./model.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { CircleCheck, CircleX, GitCommitHorizontal } from "lucide-react";
5
+ //#region src/assistant-dock/result-card.tsx
6
+ const headings = {
7
+ live: "Live now",
8
+ failed: "The change failed",
9
+ capped: "The change hit its limit"
10
+ };
11
+ /** How a job ended: live with its commit, or failed or capped at no cost, with the database changes that stay live. */
12
+ const ResultCard = ({ result }) => {
13
+ const live = result.outcome === "live";
14
+ const Icon = live ? CircleCheck : CircleX;
15
+ return /* @__PURE__ */ jsxs("section", {
16
+ "aria-label": headings[result.outcome],
17
+ className: cn("shrink-0 overflow-hidden rounded-lg border bg-background shadow-sm", live ? "border-success/40" : "border-danger/40"),
18
+ children: [/* @__PURE__ */ jsxs("header", {
19
+ className: cn("flex items-center gap-2 px-3 py-2", live ? "bg-success-soft text-success-text" : "bg-danger-soft text-danger-text"),
20
+ children: [
21
+ /* @__PURE__ */ jsx(Icon, { className: "size-3.5" }),
22
+ /* @__PURE__ */ jsx("h3", {
23
+ className: "flex-1 text-[13px] font-semibold",
24
+ children: headings[result.outcome]
25
+ }),
26
+ /* @__PURE__ */ jsx("span", {
27
+ className: "text-xs font-medium",
28
+ children: live ? formatCredits(result.credits) : "No charge"
29
+ })
30
+ ]
31
+ }), /* @__PURE__ */ jsxs("div", {
32
+ className: "space-y-2.5 px-3 py-2.5",
33
+ children: [
34
+ /* @__PURE__ */ jsx("p", {
35
+ className: "text-[13px]",
36
+ children: result.summary
37
+ }),
38
+ result.commit && /* @__PURE__ */ jsxs("div", {
39
+ className: "flex items-center gap-2 text-xs text-muted-foreground",
40
+ children: [/* @__PURE__ */ jsx(GitCommitHorizontal, { className: "size-3.5" }), result.commit.url ? /* @__PURE__ */ jsx("a", {
41
+ className: "font-mono text-primary-text hover:underline",
42
+ href: result.commit.url,
43
+ target: "_blank",
44
+ rel: "noreferrer",
45
+ children: result.commit.sha.slice(0, 7)
46
+ }) : /* @__PURE__ */ jsx("span", {
47
+ className: "font-mono",
48
+ children: result.commit.sha.slice(0, 7)
49
+ })]
50
+ }),
51
+ result.keptOnBranch && /* @__PURE__ */ jsxs("p", {
52
+ className: "text-xs text-muted-foreground",
53
+ children: [
54
+ "GitHub did not accept the push. The code is kept on the branch ",
55
+ /* @__PURE__ */ jsx("code", {
56
+ className: "font-mono",
57
+ children: result.keptOnBranch
58
+ }),
59
+ "."
60
+ ]
61
+ }),
62
+ result.dbChanges.length > 0 && /* @__PURE__ */ jsxs("div", {
63
+ className: "space-y-1",
64
+ children: [/* @__PURE__ */ jsx("div", {
65
+ className: "text-xs font-semibold",
66
+ children: live ? "Database changes" : "Database changes that stay live"
67
+ }), /* @__PURE__ */ jsx("ul", {
68
+ className: "space-y-0.5 font-mono text-[11px] text-muted-foreground",
69
+ children: result.dbChanges.map((change) => /* @__PURE__ */ jsx("li", { children: change }, change))
70
+ })]
71
+ })
72
+ ]
73
+ })]
74
+ });
75
+ };
76
+ //#endregion
77
+ export { ResultCard };
@@ -0,0 +1,7 @@
1
+ //#region src/assistant-panel/content.d.ts
2
+ export type StepState = 'done' | 'running' | 'failed';
3
+ export type Step = {
4
+ label: string;
5
+ state: StepState;
6
+ };
7
+ //#endregion
@@ -0,0 +1,30 @@
1
+ import { cn } from "../lib/cn.js";
2
+ import { Spinner } from "../primitives/spinner.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { Check, X } from "lucide-react";
5
+ //#region src/assistant-panel/step-list.tsx
6
+ const StepList = ({ steps }) => /* @__PURE__ */ jsx("ul", {
7
+ className: "shrink-0 space-y-2",
8
+ children: steps.map((step) => /* @__PURE__ */ jsxs("li", {
9
+ className: "flex items-start gap-2.5 text-[13px]",
10
+ children: [/* @__PURE__ */ jsxs("span", {
11
+ className: "mt-0.5 flex size-4 shrink-0 items-center justify-center",
12
+ children: [
13
+ step.state === "running" && /* @__PURE__ */ jsx(Spinner, { className: "size-3.5 text-primary" }),
14
+ step.state === "done" && /* @__PURE__ */ jsx(Check, {
15
+ className: "size-3.5 text-success",
16
+ strokeWidth: 2.5
17
+ }),
18
+ step.state === "failed" && /* @__PURE__ */ jsx(X, {
19
+ className: "size-3.5 text-danger",
20
+ strokeWidth: 2.5
21
+ })
22
+ ]
23
+ }), /* @__PURE__ */ jsx("span", {
24
+ className: cn(step.state === "running" ? "text-foreground" : "text-muted-foreground", step.state === "failed" && "text-danger-text"),
25
+ children: step.label
26
+ })]
27
+ }, step.label))
28
+ });
29
+ //#endregion
30
+ export { StepList };
@@ -0,0 +1,70 @@
1
+ import { Spinner } from "../primitives/spinner.js";
2
+ import { useAuth } from "./auth-provider.js";
3
+ import { Button } from "../primitives/button.js";
4
+ import { FirstRunPage } from "./first-run-page.js";
5
+ import { readResetLink, withoutResetLink } from "./reset-link.js";
6
+ import { ForgotPasswordPage } from "./forgot-password-page.js";
7
+ import { ResetPasswordPage } from "./reset-password-page.js";
8
+ import { SignInPage } from "./sign-in-page.js";
9
+ import { useEffect, useState } from "react";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ //#region src/auth/auth-gate.tsx
12
+ const firstPage = () => {
13
+ const link = readResetLink(window.location.href);
14
+ return link ? {
15
+ kind: "reset-password",
16
+ token: link.token
17
+ } : { kind: "sign-in" };
18
+ };
19
+ const forgetResetLink = () => window.history.replaceState(window.history.state, "", withoutResetLink(window.location.href));
20
+ /** Shows the app when the user is signed in (or the server has no login), and the right page otherwise. */
21
+ const AuthGate = ({ workspace, children }) => {
22
+ const { state, recheck, passwordReset, signOut } = useAuth();
23
+ const [page, setPage] = useState(firstPage);
24
+ useEffect(() => {
25
+ if (state.kind === "signed-in") setPage((current) => current.kind === "sign-in" && current.notice ? { kind: "sign-in" } : current);
26
+ }, [state.kind]);
27
+ const leaveResetPage = (next) => {
28
+ forgetResetLink();
29
+ setPage(next);
30
+ };
31
+ const passwordChanged = async () => {
32
+ if (state.kind === "signed-in") await signOut();
33
+ leaveResetPage({
34
+ kind: "sign-in",
35
+ notice: "Your password is changed. Sign in with the new one."
36
+ });
37
+ };
38
+ switch (state.kind) {
39
+ case "loading": return /* @__PURE__ */ jsxs("div", {
40
+ className: "flex h-full min-h-48 items-center justify-center gap-2 text-[13px] text-muted-foreground",
41
+ children: [/* @__PURE__ */ jsx(Spinner, {}), " Loading"]
42
+ });
43
+ case "error": return /* @__PURE__ */ jsxs("div", {
44
+ className: "flex h-full min-h-48 flex-col items-center justify-center gap-3 p-8 text-center",
45
+ children: [/* @__PURE__ */ jsxs("p", {
46
+ className: "text-[13px] text-muted-foreground",
47
+ children: ["Cannot reach the server: ", state.message]
48
+ }), /* @__PURE__ */ jsx(Button, {
49
+ onClick: recheck,
50
+ children: "Try again"
51
+ })]
52
+ });
53
+ case "needs-admin": return /* @__PURE__ */ jsx(FirstRunPage, { onCheckAgain: recheck });
54
+ }
55
+ if (page.kind === "reset-password") return /* @__PURE__ */ jsx(ResetPasswordPage, {
56
+ token: page.token,
57
+ onDone: () => void passwordChanged(),
58
+ onRequestNewLink: passwordReset ? () => leaveResetPage({ kind: "forgot-password" }) : void 0,
59
+ onBack: () => leaveResetPage({ kind: "sign-in" })
60
+ });
61
+ if (state.kind === "signed-in") return children;
62
+ if (page.kind === "forgot-password") return /* @__PURE__ */ jsx(ForgotPasswordPage, { onBack: () => setPage({ kind: "sign-in" }) });
63
+ return /* @__PURE__ */ jsx(SignInPage, {
64
+ workspace,
65
+ notice: page.notice,
66
+ onForgotPassword: passwordReset ? () => setPage({ kind: "forgot-password" }) : void 0
67
+ });
68
+ };
69
+ //#endregion
70
+ export { AuthGate };
@@ -0,0 +1,29 @@
1
+ import { LogoMark } from "../app-shell/logo.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/auth/auth-layout.tsx
4
+ /** The frame of the sign-in and first-run pages: logo, title and a card. */
5
+ const AuthLayout = ({ title, description, children }) => /* @__PURE__ */ jsx("div", {
6
+ className: "flex min-h-full w-full items-center justify-center bg-surface px-4 py-10",
7
+ children: /* @__PURE__ */ jsxs("div", {
8
+ className: "w-full max-w-sm",
9
+ children: [/* @__PURE__ */ jsxs("div", {
10
+ className: "mb-6 flex flex-col items-center text-center",
11
+ children: [
12
+ /* @__PURE__ */ jsx(LogoMark, { className: "size-10" }),
13
+ /* @__PURE__ */ jsx("h1", {
14
+ className: "mt-4 text-xl font-semibold tracking-tight",
15
+ children: title
16
+ }),
17
+ description && /* @__PURE__ */ jsx("p", {
18
+ className: "mt-1 text-[13px] text-muted-foreground",
19
+ children: description
20
+ })
21
+ ]
22
+ }), /* @__PURE__ */ jsx("div", {
23
+ className: "rounded-lg border bg-background p-5 shadow-sm",
24
+ children
25
+ })]
26
+ })
27
+ });
28
+ //#endregion
29
+ export { AuthLayout };
@@ -0,0 +1,71 @@
1
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { createAuthSession } from "@protobase/client";
4
+ //#region src/auth/auth-provider.tsx
5
+ const AuthContext = createContext(null);
6
+ const useAuth = () => {
7
+ const api = useContext(AuthContext);
8
+ if (!api) throw new Error("useAuth must be used inside AuthProvider");
9
+ return api;
10
+ };
11
+ /** Finds out whether the server has a login and who is signed in, and keeps that state for the app. */
12
+ const AuthProvider = ({ session: given, children }) => {
13
+ const [state, setState] = useState({ kind: "loading" });
14
+ const [passwordReset, setPasswordReset] = useState(false);
15
+ const [socialProviders, setSocialProviders] = useState([]);
16
+ const markSignedOut = useCallback(() => setState((current) => current.kind === "signed-in" ? { kind: "signed-out" } : current), []);
17
+ const signedOut = useRef(markSignedOut);
18
+ signedOut.current = markSignedOut;
19
+ const session = useMemo(() => given ?? createAuthSession({ onSignedOut: () => signedOut.current() }), [given]);
20
+ const recheck = useCallback(() => {
21
+ setState({ kind: "loading" });
22
+ session.status().then(async (status) => {
23
+ setPasswordReset(status.passwordReset);
24
+ setSocialProviders(status.socialProviders);
25
+ if (status.needsAdmin) return setState({ kind: "needs-admin" });
26
+ const user = await session.session();
27
+ setState(user ? {
28
+ kind: "signed-in",
29
+ user
30
+ } : { kind: "signed-out" });
31
+ }, (error) => setState({
32
+ kind: "error",
33
+ message: error instanceof Error ? error.message : "The server did not answer"
34
+ }));
35
+ }, [session]);
36
+ useEffect(recheck, [recheck]);
37
+ const api = useMemo(() => ({
38
+ state,
39
+ session,
40
+ passwordReset,
41
+ socialProviders,
42
+ markSignedOut,
43
+ recheck,
44
+ signIn: async (email, password) => {
45
+ setState({
46
+ kind: "signed-in",
47
+ user: await session.signIn(email, password)
48
+ });
49
+ },
50
+ signInSocial: async (provider) => {
51
+ window.location.assign(await session.signInSocial(provider, window.location.href));
52
+ },
53
+ signOut: async () => {
54
+ await session.signOut();
55
+ setState({ kind: "signed-out" });
56
+ }
57
+ }), [
58
+ state,
59
+ session,
60
+ passwordReset,
61
+ socialProviders,
62
+ markSignedOut,
63
+ recheck
64
+ ]);
65
+ return /* @__PURE__ */ jsx(AuthContext.Provider, {
66
+ value: api,
67
+ children
68
+ });
69
+ };
70
+ //#endregion
71
+ export { AuthProvider, useAuth };
@@ -0,0 +1,51 @@
1
+ import { Button } from "../primitives/button.js";
2
+ import { AuthLayout } from "./auth-layout.js";
3
+ import { useState } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { Check, Copy } from "lucide-react";
6
+ //#region src/auth/first-run-page.tsx
7
+ const createAdminCommand = "protobase users create you@example.com";
8
+ /**
9
+ * Shown while the admin store has no user. Accounts are not created over the web (a fresh URL must not be claimable
10
+ * by whoever reaches it first), so this says how to create the first admin on the host.
11
+ */
12
+ const FirstRunPage = ({ command = createAdminCommand, onCheckAgain, busy }) => {
13
+ const [copied, setCopied] = useState(false);
14
+ return /* @__PURE__ */ jsxs(AuthLayout, {
15
+ title: "No admin yet",
16
+ description: "This Protobase has no users to sign in with.",
17
+ children: [
18
+ /* @__PURE__ */ jsx("p", {
19
+ className: "text-[13px] text-muted-foreground",
20
+ children: "Run this in the terminal where Protobase runs, then reload. The first admin is created on the server, not in the browser, so nobody else can claim a fresh URL."
21
+ }),
22
+ /* @__PURE__ */ jsxs("div", {
23
+ className: "mt-4 flex items-center gap-2 rounded-md border bg-surface py-1.5 pl-3 pr-1.5",
24
+ children: [/* @__PURE__ */ jsx("code", {
25
+ className: "min-w-0 flex-1 break-all font-mono text-xs",
26
+ children: command
27
+ }), /* @__PURE__ */ jsx("button", {
28
+ type: "button",
29
+ "aria-label": "Copy command",
30
+ onClick: () => {
31
+ navigator.clipboard.writeText(command).then(() => {
32
+ setCopied(true);
33
+ setTimeout(() => setCopied(false), 2e3);
34
+ });
35
+ },
36
+ className: "inline-flex size-8 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-muted pointer-coarse:size-11",
37
+ children: copied ? /* @__PURE__ */ jsx(Check, { className: "size-4 text-success" }) : /* @__PURE__ */ jsx(Copy, { className: "size-4" })
38
+ })]
39
+ }),
40
+ /* @__PURE__ */ jsx(Button, {
41
+ variant: "primary",
42
+ className: "mt-4 min-h-10 w-full",
43
+ loading: busy,
44
+ onClick: onCheckAgain,
45
+ children: "Reload"
46
+ })
47
+ ]
48
+ });
49
+ };
50
+ //#endregion
51
+ export { FirstRunPage, createAdminCommand };
@@ -0,0 +1,75 @@
1
+ import { Input } from "../primitives/input.js";
2
+ import { Button } from "../primitives/button.js";
3
+ import { AuthLayout } from "./auth-layout.js";
4
+ import { useState } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/auth/forgot-password-form.tsx
7
+ const ForgotPasswordForm = ({ onSubmit, onBack, busy, error, sentTo }) => {
8
+ const [email, setEmail] = useState("");
9
+ if (sentTo) return /* @__PURE__ */ jsxs(AuthLayout, {
10
+ title: "Check your email",
11
+ children: [/* @__PURE__ */ jsxs("p", {
12
+ className: "text-[13px] text-muted-foreground",
13
+ children: [
14
+ "If ",
15
+ /* @__PURE__ */ jsx("span", {
16
+ className: "font-medium text-foreground",
17
+ children: sentTo
18
+ }),
19
+ " has an account, an email with a link to choose a new password is on its way. The link works once, for a limited time."
20
+ ]
21
+ }), /* @__PURE__ */ jsx(Button, {
22
+ variant: "primary",
23
+ className: "mt-4 min-h-10 w-full",
24
+ onClick: onBack,
25
+ children: "Back to sign in"
26
+ })]
27
+ });
28
+ return /* @__PURE__ */ jsx(AuthLayout, {
29
+ title: "Reset your password",
30
+ description: "We email you a link to choose a new one.",
31
+ children: /* @__PURE__ */ jsxs("form", {
32
+ className: "flex flex-col gap-4",
33
+ onSubmit: (event) => {
34
+ event.preventDefault();
35
+ onSubmit(email.trim());
36
+ },
37
+ children: [
38
+ /* @__PURE__ */ jsxs("label", {
39
+ className: "flex flex-col gap-1.5 text-[13px] font-medium",
40
+ children: ["Email", /* @__PURE__ */ jsx(Input, {
41
+ type: "email",
42
+ name: "email",
43
+ autoComplete: "username",
44
+ required: true,
45
+ autoFocus: true,
46
+ value: email,
47
+ invalid: Boolean(error),
48
+ onChange: (event) => setEmail(event.target.value),
49
+ className: "min-h-6"
50
+ })]
51
+ }),
52
+ error && /* @__PURE__ */ jsx("p", {
53
+ role: "alert",
54
+ className: "text-xs font-medium text-danger-text",
55
+ children: error
56
+ }),
57
+ /* @__PURE__ */ jsx(Button, {
58
+ variant: "primary",
59
+ type: "submit",
60
+ loading: busy,
61
+ className: "min-h-10",
62
+ children: "Send reset link"
63
+ }),
64
+ /* @__PURE__ */ jsx(Button, {
65
+ variant: "ghost",
66
+ onClick: onBack,
67
+ className: "min-h-10",
68
+ children: "Back to sign in"
69
+ })
70
+ ]
71
+ })
72
+ });
73
+ };
74
+ //#endregion
75
+ export { ForgotPasswordForm };