smoodly 0.0.23 → 0.0.25

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 (135) hide show
  1. package/README.md +192 -43
  2. package/bin/smoodly.js +6 -0
  3. package/dist/admin/auth.d.ts +10 -5
  4. package/dist/admin/auth.js +14 -13
  5. package/dist/admin/client-ops.js +2 -1
  6. package/dist/admin/editor/EditorView.js +20 -6
  7. package/dist/admin/editor/InsertPicker.d.ts +14 -0
  8. package/dist/admin/editor/InsertPicker.js +37 -7
  9. package/dist/admin/editor/InspectorFields.js +1 -1
  10. package/dist/admin/editor/Outline.js +2 -2
  11. package/dist/admin/editor/PageSettings.js +6 -0
  12. package/dist/admin/editor/SeoSettings.js +1 -1
  13. package/dist/admin/editor/locale-url.d.ts +7 -4
  14. package/dist/admin/editor/locale-url.js +14 -7
  15. package/dist/admin/editor/usePageLocale.js +9 -1
  16. package/dist/admin/forms/CropEditor.d.ts +4 -2
  17. package/dist/admin/forms/CropEditor.js +6 -3
  18. package/dist/admin/forms/DatePicker.d.ts +9 -0
  19. package/dist/admin/forms/DatePicker.js +133 -0
  20. package/dist/admin/forms/FieldWidget.js +11 -5
  21. package/dist/admin/forms/ImagePreview.d.ts +9 -0
  22. package/dist/admin/forms/ImagePreview.js +11 -0
  23. package/dist/admin/forms/MediaWidget.d.ts +8 -2
  24. package/dist/admin/forms/MediaWidget.js +13 -13
  25. package/dist/admin/forms/RichtextElementCard.js +1 -1
  26. package/dist/admin/forms/RichtextImageCard.js +14 -9
  27. package/dist/admin/forms/date-picker.d.ts +44 -0
  28. package/dist/admin/forms/date-picker.js +117 -0
  29. package/dist/admin/forms/elements-context.d.ts +3 -0
  30. package/dist/admin/forms/elements-context.js +7 -2
  31. package/dist/admin/forms/time-zone-context.d.ts +7 -0
  32. package/dist/admin/forms/time-zone-context.js +15 -0
  33. package/dist/admin/forms/useRichtextEditor.js +10 -1
  34. package/dist/admin/index.d.ts +2 -0
  35. package/dist/admin/next/bridge.d.ts +2 -1
  36. package/dist/admin/next/bridge.js +35 -5
  37. package/dist/admin/next/index.d.ts +2 -0
  38. package/dist/admin/ops-impl.js +73 -1
  39. package/dist/admin/ops.d.ts +22 -2
  40. package/dist/admin/serialize.d.ts +2 -0
  41. package/dist/admin/serialize.js +1 -0
  42. package/dist/admin/shell/AdminApp.js +48 -3
  43. package/dist/admin/shell/EntriesList.js +12 -21
  44. package/dist/admin/shell/EntryForm.js +63 -5
  45. package/dist/admin/shell/GlobalForm.js +22 -2
  46. package/dist/admin/shell/GlobalList.js +4 -18
  47. package/dist/admin/shell/IconRail.d.ts +3 -1
  48. package/dist/admin/shell/IconRail.js +17 -7
  49. package/dist/admin/shell/InviteModal.d.ts +6 -0
  50. package/dist/admin/shell/InviteModal.js +28 -0
  51. package/dist/admin/shell/ListView.js +2 -2
  52. package/dist/admin/shell/LoginView.js +34 -5
  53. package/dist/admin/shell/MediaScreen.js +11 -1
  54. package/dist/admin/shell/MembersPage.d.ts +2 -1
  55. package/dist/admin/shell/MembersPage.js +90 -25
  56. package/dist/admin/shell/PageDialogs.d.ts +26 -0
  57. package/dist/admin/shell/PageDialogs.js +38 -0
  58. package/dist/admin/shell/PagesList.js +100 -70
  59. package/dist/admin/shell/RouteLink.js +1 -1
  60. package/dist/admin/shell/SchemaNotice.d.ts +4 -0
  61. package/dist/admin/shell/SchemaNotice.js +4 -0
  62. package/dist/admin/shell/SetPasswordView.d.ts +7 -0
  63. package/dist/admin/shell/SetPasswordView.js +41 -0
  64. package/dist/admin/shell/SettingsView.d.ts +5 -1
  65. package/dist/admin/shell/SettingsView.js +17 -11
  66. package/dist/admin/shell/live-url.d.ts +10 -0
  67. package/dist/admin/shell/live-url.js +19 -0
  68. package/dist/admin/shell/locale-memory.d.ts +8 -0
  69. package/dist/admin/shell/locale-memory.js +23 -0
  70. package/dist/admin/shell/members-list.d.ts +15 -1
  71. package/dist/admin/shell/members-list.js +24 -7
  72. package/dist/admin/shell/page-dialogs.d.ts +9 -0
  73. package/dist/admin/shell/page-dialogs.js +19 -0
  74. package/dist/admin/shell/router.d.ts +10 -7
  75. package/dist/admin/shell/router.js +18 -9
  76. package/dist/admin/shell/session.d.ts +6 -0
  77. package/dist/admin/shell/session.js +17 -2
  78. package/dist/admin/shell/settings.d.ts +0 -6
  79. package/dist/admin/shell/settings.js +4 -5
  80. package/dist/admin/supabase-auth.js +6 -5
  81. package/dist/admin/tree-ops.js +3 -0
  82. package/dist/admin/ui/ActionMenu.js +26 -6
  83. package/dist/admin/ui/Combobox.d.ts +5 -0
  84. package/dist/admin/ui/Combobox.js +1 -1
  85. package/dist/admin/ui/ConfirmDialog.d.ts +8 -0
  86. package/dist/admin/ui/ConfirmDialog.js +30 -0
  87. package/dist/admin/ui/Dialog.d.ts +13 -0
  88. package/dist/admin/ui/Dialog.js +54 -0
  89. package/dist/admin/ui/HistoryModal.d.ts +2 -1
  90. package/dist/admin/ui/HistoryModal.js +7 -2
  91. package/dist/admin/ui/ResizeHandle.js +4 -4
  92. package/dist/admin/ui/confirm.d.ts +35 -0
  93. package/dist/admin/ui/confirm.js +57 -0
  94. package/dist/admin/ui/primitives.d.ts +19 -4
  95. package/dist/admin/ui/primitives.js +11 -5
  96. package/dist/admin/ui/resize.d.ts +7 -4
  97. package/dist/admin/ui/resize.js +7 -4
  98. package/dist/admin/ui/theme.d.ts +1 -1
  99. package/dist/admin/ui/theme.js +368 -203
  100. package/dist/admin/validate.d.ts +4 -0
  101. package/dist/admin/validate.js +28 -0
  102. package/dist/cli/load-collections.d.ts +2 -0
  103. package/dist/cli/load-collections.js +26 -0
  104. package/dist/cli/main.d.ts +13 -0
  105. package/dist/cli/main.js +62 -0
  106. package/dist/cli/migrate.d.ts +23 -0
  107. package/dist/cli/migrate.js +64 -0
  108. package/dist/config.d.ts +4 -0
  109. package/dist/config.js +9 -0
  110. package/dist/fields.d.ts +2 -0
  111. package/dist/fields.js +2 -0
  112. package/dist/index.d.ts +6 -3
  113. package/dist/index.js +4 -2
  114. package/dist/marks.d.ts +3 -0
  115. package/dist/marks.js +3 -0
  116. package/dist/member-store.d.ts +30 -1
  117. package/dist/member-store.js +37 -6
  118. package/dist/migrations.d.ts +37 -0
  119. package/dist/migrations.js +73 -0
  120. package/dist/permissions.d.ts +7 -0
  121. package/dist/permissions.js +19 -0
  122. package/dist/schema-version.d.ts +17 -0
  123. package/dist/schema-version.js +32 -0
  124. package/dist/sql-space.js +6 -0
  125. package/dist/sql.js +10 -0
  126. package/dist/store.d.ts +3 -0
  127. package/dist/store.js +4 -0
  128. package/dist/supabase-member-store.d.ts +8 -1
  129. package/dist/supabase-member-store.js +62 -2
  130. package/dist/supabase-store.d.ts +3 -0
  131. package/dist/supabase-store.js +23 -0
  132. package/dist/time-zone.d.ts +6 -0
  133. package/dist/time-zone.js +27 -0
  134. package/migrations/20260926120000_smoodly_v1.sql +535 -0
  135. package/package.json +13 -3
@@ -0,0 +1,57 @@
1
+ // The admin's one "are you sure" (design_handoff_smoodly_neutral/screens/
2
+ // ConfirmModal.md): a caller awaits `confirm(...)` and gets a boolean, the
3
+ // way it awaited window.confirm — but the question is answered in
4
+ // Smoodly's own dialog, rendered by <ConfirmHost/> (ConfirmDialog.tsx)
5
+ // from this store. Framework-free on purpose: the router (shell/router.ts)
6
+ // asks from outside React.
7
+ //
8
+ // One question at a time; a second request waits for the first answer.
9
+ // Without a mounted host (a test, a stray render outside the shell) the
10
+ // question falls back to window.confirm so no caller can hang.
11
+ /** The Discard changes question, shared by the page editor and the entry and global forms (publish flow §4). */
12
+ export const DISCARD_CONFIRM = {
13
+ title: "Discard unsaved changes?",
14
+ body: "The draft goes back to the published version. Every edit since the last publish is lost.",
15
+ confirmLabel: "Discard changes",
16
+ destructive: true,
17
+ };
18
+ const listeners = new Set();
19
+ let queue = [];
20
+ let seq = 0;
21
+ const notify = () => { for (const fn of listeners)
22
+ fn(); };
23
+ /** Ask; resolves with the answer once the dialog closes. */
24
+ export function confirm(options) {
25
+ if (listeners.size === 0) {
26
+ const text = [options.title, options.body].filter(Boolean).join("\n\n");
27
+ return Promise.resolve(typeof window !== "undefined" && typeof window.confirm === "function" ? window.confirm(text) : false);
28
+ }
29
+ return new Promise((resolve) => {
30
+ const id = ++seq;
31
+ const request = {
32
+ id,
33
+ options,
34
+ answer(yes) {
35
+ if (!queue.some((r) => r.id === id))
36
+ return; // answered already
37
+ queue = queue.filter((r) => r.id !== id);
38
+ notify();
39
+ resolve(yes);
40
+ },
41
+ };
42
+ queue = [...queue, request];
43
+ notify();
44
+ });
45
+ }
46
+ /** The question being asked, if any — the head of the queue. */
47
+ export const pendingConfirm = () => queue[0] ?? null;
48
+ /** For useSyncExternalStore: fires on every queue change. */
49
+ export function subscribeConfirm(fn) {
50
+ listeners.add(fn);
51
+ return () => { listeners.delete(fn); };
52
+ }
53
+ /** Test seam: drop every pending question unanswered. */
54
+ export function resetConfirms() {
55
+ queue = [];
56
+ notify();
57
+ }
@@ -13,13 +13,16 @@ export declare function Logo({ height }: {
13
13
  * is right on the very first paint with no state and no hydration mismatch. */
14
14
  export declare function ThemeToggle(): import("react").JSX.Element;
15
15
  type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
16
- variant?: "primary" | "secondary" | "ghost" | "danger" | "ok";
16
+ /** `danger` is the quiet red-text button; `destructive` the filled red primary of a confirm dialog. */
17
+ variant?: "primary" | "secondary" | "ghost" | "danger" | "destructive" | "ok";
17
18
  size?: "sm" | "md" | "lg";
18
19
  };
19
20
  export declare function Button({ variant, size, className, ...rest }: ButtonProps): import("react").JSX.Element;
20
21
  /** The same shape as Button, for navigations that must stay real links. */
21
- export declare function LinkButton({ variant, children, ...rest }: {
22
+ export declare function LinkButton({ variant, icon, children, ...rest }: {
22
23
  variant?: ButtonProps["variant"];
24
+ /** An icon-only pill (a square the button's height); give it an aria-label. */
25
+ icon?: boolean;
23
26
  children: ReactNode;
24
27
  } & AnchorHTMLAttributes<HTMLAnchorElement>): import("react").JSX.Element;
25
28
  export declare function IconButton({ label, tone, size, children, ...rest }: {
@@ -102,6 +105,17 @@ export declare function Segmented<T extends string>({ options, value, onChange,
102
105
  className?: string;
103
106
  } | undefined;
104
107
  }): import("react").JSX.Element;
108
+ /** A pill row (the f.radio look, spec 2026-09-14 §5): no track, the chosen pill filled in the
109
+ * soft accent. For a small choice inside a table row, where a segmented control reads as tabs. */
110
+ export declare function Pills<T extends string>({ options, value, onChange, disabled, title, label }: {
111
+ options: readonly T[];
112
+ value: T;
113
+ onChange(v: T): void;
114
+ disabled?: boolean;
115
+ /** Why the row is off, on every pill. */
116
+ title?: string;
117
+ label?(o: T): ReactNode;
118
+ }): import("react").JSX.Element;
105
119
  export declare function StatusBadge({ status }: {
106
120
  status: string;
107
121
  }): import("react").JSX.Element;
@@ -109,10 +123,11 @@ export declare function StatusBadge({ status }: {
109
123
  export declare function SaveIndicator({ state }: {
110
124
  state: SaveState;
111
125
  }): import("react").JSX.Element;
112
- export declare function SectionLabel({ children, action, mono, style }: {
126
+ /** `caps`: the overline style — small, uppercase, tracked (2026-09-26; the mono face before). */
127
+ export declare function SectionLabel({ children, action, caps, style }: {
113
128
  children: ReactNode;
114
129
  action?: ReactNode;
115
- mono?: boolean;
130
+ caps?: boolean;
116
131
  style?: CSSProperties;
117
132
  }): import("react").JSX.Element;
118
133
  export declare function Divider({ bleed }: {
@@ -45,8 +45,8 @@ export function Button({ variant = "primary", size = "md", className, ...rest })
45
45
  return (_jsx("button", { type: "button", className: cx("sm-btn", `sm-btn--${variant}`, size === "lg" && "sm-btn--lg", size === "sm" && "sm-btn--sm", className), ...rest }));
46
46
  }
47
47
  /** The same shape as Button, for navigations that must stay real links. */
48
- export function LinkButton({ variant = "secondary", children, ...rest }) {
49
- return _jsx("a", { className: cx("sm-btn", `sm-btn--${variant}`), ...rest, children: children });
48
+ export function LinkButton({ variant = "secondary", icon = false, children, ...rest }) {
49
+ return _jsx("a", { className: cx("sm-btn", `sm-btn--${variant}`, icon && "sm-btn--icon"), ...rest, children: children });
50
50
  }
51
51
  export function IconButton({ label, tone = "default", size = 24, children, ...rest }) {
52
52
  return (_jsx("button", { type: "button", "aria-label": label, title: label, className: cx("sm-iconbtn", tone === "inverse" && "sm-iconbtn--inverse"), style: { width: size, height: size }, ...rest, children: children }));
@@ -105,6 +105,11 @@ export function Segmented({ options, value, onChange, stretch, disabled, label,
105
105
  return (_jsx("button", { type: "button", className: cx("sm-seg__opt", extra?.className), "aria-pressed": o === value, disabled: disabled || extra?.disabled, title: extra?.title, onClick: () => onChange(o), children: label ? label(o) : o }, o));
106
106
  }) }));
107
107
  }
108
+ /** A pill row (the f.radio look, spec 2026-09-14 §5): no track, the chosen pill filled in the
109
+ * soft accent. For a small choice inside a table row, where a segmented control reads as tabs. */
110
+ export function Pills({ options, value, onChange, disabled, title, label }) {
111
+ return (_jsx("div", { className: cx("sm-pills", disabled && "sm-pills--disabled"), role: "radiogroup", children: options.map((o) => (_jsx("button", { type: "button", className: "sm-pill", "aria-pressed": o === value, disabled: disabled, title: title, onClick: () => onChange(o), children: label ? label(o) : o }, o))) }));
112
+ }
108
113
  /* ── data ────────────────────────────────────────────────────────────── */
109
114
  // Keyed by the product labels of ui/status-label.ts (lowercased by StatusBadge).
110
115
  const STATUS_TONE = {
@@ -126,10 +131,11 @@ export function SaveIndicator({ state }) {
126
131
  error: { label: "Save failed — retrying", dot: "var(--danger)", color: "var(--danger)" },
127
132
  };
128
133
  const { label, dot, color } = map[state];
129
- return (_jsxs("span", { className: "sm-savestate", style: { color }, title: label, children: [_jsx("span", { className: "sm-dot", style: { background: dot } }), label] }));
134
+ return (_jsxs("span", { className: "sm-savestate", "data-testid": "save-status", style: { color }, title: label, children: [_jsx("span", { className: "sm-dot", style: { background: dot } }), label] }));
130
135
  }
131
- export function SectionLabel({ children, action, mono, style }) {
132
- return (_jsxs("div", { className: cx("sm-sectionlabel", mono && "sm-sectionlabel--mono"), style: style, children: [_jsx("span", { className: "sm-label", children: children }), action && _jsx("span", { style: { color: "var(--accent)", fontWeight: 600, fontSize: "var(--text-sm)" }, children: action })] }));
136
+ /** `caps`: the overline style — small, uppercase, tracked (2026-09-26; the mono face before). */
137
+ export function SectionLabel({ children, action, caps, style }) {
138
+ return (_jsxs("div", { className: cx("sm-sectionlabel", caps && "sm-sectionlabel--caps"), style: style, children: [_jsx("span", { className: "sm-label", children: children }), action && _jsx("span", { style: { color: "var(--accent)", fontWeight: 600, fontSize: "var(--text-sm)" }, children: action })] }));
133
139
  }
134
140
  export function Divider({ bleed = 0 }) {
135
141
  return _jsx("div", { className: "sm-divider", style: { margin: `0 -${bleed}px` } });
@@ -6,10 +6,13 @@ export declare const MIN_INSPECTOR_W = 260;
6
6
  export declare const DEFAULT_INSPECTOR_W = 360;
7
7
  /** One arrow-key press. */
8
8
  export declare const RESIZE_STEP = 16;
9
- /** Half the editor row (canvas + handle + inspector), never below the minimum. */
10
- export declare const maxInspectorW: (rowWidth: number) => number;
11
- /** An integer inside [MIN, half the row]. */
12
- export declare const clampInspectorW: (px: number, rowWidth: number) => number;
9
+ /** What the canvas keeps however far the inspector is dragged (2026-09-26, up from half the row; 340 for an hour). */
10
+ export declare const MIN_CANVAS_W = 440;
11
+ /** The editor row (canvas + handle + inspector) less the handle and the
12
+ * canvas minimum, never below the inspector minimum. */
13
+ export declare const maxInspectorW: (rowWidth: number, handleWidth?: number) => number;
14
+ /** An integer inside [MIN, the row less the handle and the canvas minimum]. */
15
+ export declare const clampInspectorW: (px: number, rowWidth: number, handleWidth?: number) => number;
13
16
  /** A stored value is trusted only as a plain integer ≥ MIN; anything else is
14
17
  * ignored (never clamped or rewritten). The boot script in theme.tsx repeats
15
18
  * this test inline — keep the two in step. */
@@ -8,10 +8,13 @@ export const MIN_INSPECTOR_W = 260;
8
8
  export const DEFAULT_INSPECTOR_W = 360;
9
9
  /** One arrow-key press. */
10
10
  export const RESIZE_STEP = 16;
11
- /** Half the editor row (canvas + handle + inspector), never below the minimum. */
12
- export const maxInspectorW = (rowWidth) => Math.max(MIN_INSPECTOR_W, Math.floor(rowWidth / 2));
13
- /** An integer inside [MIN, half the row]. */
14
- export const clampInspectorW = (px, rowWidth) => Math.min(maxInspectorW(rowWidth), Math.max(MIN_INSPECTOR_W, Math.round(px)));
11
+ /** What the canvas keeps however far the inspector is dragged (2026-09-26, up from half the row; 340 for an hour). */
12
+ export const MIN_CANVAS_W = 440;
13
+ /** The editor row (canvas + handle + inspector) less the handle and the
14
+ * canvas minimum, never below the inspector minimum. */
15
+ export const maxInspectorW = (rowWidth, handleWidth = 0) => Math.max(MIN_INSPECTOR_W, Math.floor(rowWidth - handleWidth - MIN_CANVAS_W));
16
+ /** An integer inside [MIN, the row less the handle and the canvas minimum]. */
17
+ export const clampInspectorW = (px, rowWidth, handleWidth = 0) => Math.min(maxInspectorW(rowWidth, handleWidth), Math.max(MIN_INSPECTOR_W, Math.round(px)));
15
18
  /** A stored value is trusted only as a plain integer ≥ MIN; anything else is
16
19
  * ignored (never clamped or rewritten). The boot script in theme.tsx repeats
17
20
  * this test inline — keep the two in step. */
@@ -1,7 +1,7 @@
1
1
  export declare const ADMIN_CSS: string;
2
2
  /** Where the theme toggle's choice is remembered, browser-side only. */
3
3
  export declare const THEME_KEY = "smoodly-theme";
4
- /** TEMPORARY, see the technical block: which look the developer is comparing. */
4
+ /** Which look the developer is comparing (see the warm block): "warm" applies it, anything else is the default. */
5
5
  export declare const LOOK_KEY = "smoodly-look";
6
6
  /** One <style> element and the theme boot, rendered once by AdminLayout. */
7
7
  export declare function AdminStyles(): import("react").JSX.Element;