@realiizlabs/admin 0.11.2 → 0.13.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 (39) hide show
  1. package/dist/Sidebar-CNuphww6.d.cts +60 -0
  2. package/dist/Sidebar-CNuphww6.d.ts +60 -0
  3. package/dist/auth/index.d.cts +5 -127
  4. package/dist/auth/index.d.ts +5 -127
  5. package/dist/auth-ui/index.js +124 -1
  6. package/dist/auth-ui/index.js.map +1 -1
  7. package/dist/forms/index.js +287 -1
  8. package/dist/forms/index.js.map +1 -1
  9. package/dist/forms-ui/index.js +355 -6
  10. package/dist/forms-ui/index.js.map +1 -1
  11. package/dist/git/index.cjs.map +1 -1
  12. package/dist/git/index.d.cts +3 -77
  13. package/dist/git/index.d.ts +3 -77
  14. package/dist/git/index.js.map +1 -1
  15. package/dist/members-nhbRSYyr.d.cts +106 -0
  16. package/dist/members-nhbRSYyr.d.ts +106 -0
  17. package/dist/roles-BjWuj2_v.d.cts +25 -0
  18. package/dist/roles-Dnwj-DAm.d.ts +25 -0
  19. package/dist/shell/index.d.cts +3 -58
  20. package/dist/shell/index.d.ts +3 -58
  21. package/dist/studio/index.cjs +1150 -0
  22. package/dist/studio/index.cjs.map +1 -0
  23. package/dist/studio/index.d.cts +325 -0
  24. package/dist/studio/index.d.ts +325 -0
  25. package/dist/studio/index.js +1131 -0
  26. package/dist/studio/index.js.map +1 -0
  27. package/dist/studio-ui/index.cjs +3931 -0
  28. package/dist/studio-ui/index.cjs.map +1 -0
  29. package/dist/studio-ui/index.d.cts +375 -0
  30. package/dist/studio-ui/index.d.ts +375 -0
  31. package/dist/studio-ui/index.js +3900 -0
  32. package/dist/studio-ui/index.js.map +1 -0
  33. package/dist/types-BP5G1myE.d.cts +78 -0
  34. package/dist/types-BP5G1myE.d.ts +78 -0
  35. package/package.json +20 -2
  36. package/dist/chunk-2GSYBARR.js +0 -126
  37. package/dist/chunk-2GSYBARR.js.map +0 -1
  38. package/dist/chunk-JLR5K6RP.js +0 -289
  39. package/dist/chunk-JLR5K6RP.js.map +0 -1
@@ -0,0 +1,375 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { C as ContentTypeEntry } from '../types-D46UjOfv.cjs';
4
+ import { c as SiteBranding, d as SiteMember } from '../members-nhbRSYyr.cjs';
5
+ import { S as ShellUser, N as NavItem } from '../Sidebar-CNuphww6.cjs';
6
+ import { a as PullRequestStatus } from '../types-BP5G1myE.cjs';
7
+ import 'zod';
8
+ import '@supabase/supabase-js';
9
+
10
+ /**
11
+ * The serialisable shapes that cross from a site's server pages into these
12
+ * client components, and the exact set of server actions a site must provide.
13
+ */
14
+
15
+ type ActionResult = {
16
+ ok: true;
17
+ message?: string;
18
+ } | {
19
+ ok: false;
20
+ error: string;
21
+ };
22
+ type PublishResult = {
23
+ ok: false;
24
+ error: string;
25
+ };
26
+ type MergeOutcome = {
27
+ state: "merged";
28
+ sha: string;
29
+ } | {
30
+ state: "pending";
31
+ } | {
32
+ state: "stale";
33
+ } | {
34
+ state: "red";
35
+ failingCheck: string | null;
36
+ } | {
37
+ state: "error";
38
+ message: string;
39
+ };
40
+ type DiscardOutcome = {
41
+ state: "discarded";
42
+ } | {
43
+ state: "already-published";
44
+ } | {
45
+ state: "error";
46
+ message: string;
47
+ };
48
+ type TeamState = {
49
+ ok: true;
50
+ members: SiteMember[];
51
+ canInvite: boolean;
52
+ } | {
53
+ ok: false;
54
+ error: string;
55
+ };
56
+ interface ImagePayload {
57
+ path: string;
58
+ base64: string;
59
+ }
60
+ interface PublishInput {
61
+ type: string;
62
+ frontmatter: Record<string, unknown>;
63
+ body: string;
64
+ existing?: {
65
+ number: number;
66
+ branch: string;
67
+ } | null;
68
+ images?: ImagePayload[];
69
+ }
70
+ /** Exactly what a site's "use server" wrapper file must export. */
71
+ interface StudioActions {
72
+ publishContent: (input: PublishInput) => Promise<PublishResult | never>;
73
+ removeContent: (input: {
74
+ type: string;
75
+ slug: string;
76
+ }) => Promise<PublishResult | never>;
77
+ mergeContentPr: (number: number) => Promise<MergeOutcome>;
78
+ discardChange: (number: number, branch: string) => Promise<DiscardOutcome>;
79
+ previewBody: (markdown: string) => Promise<{
80
+ html: string;
81
+ } | {
82
+ error: string;
83
+ }>;
84
+ saveProfile: (input: {
85
+ name: string;
86
+ displayName: string;
87
+ }) => Promise<ActionResult>;
88
+ saveAvatar: (input: {
89
+ base64: string;
90
+ } | null) => Promise<ActionResult>;
91
+ changePassword: (input: {
92
+ password: string;
93
+ confirm: string;
94
+ }) => Promise<ActionResult>;
95
+ loadBranding: () => Promise<SiteBranding | null>;
96
+ saveBranding: (input: {
97
+ name: string;
98
+ businessEmail: string;
99
+ businessPhone: string;
100
+ }) => Promise<ActionResult>;
101
+ saveBrandingPicture: (input: {
102
+ kind: "logo" | "favicon";
103
+ base64: string | null;
104
+ }) => Promise<ActionResult>;
105
+ loadMe: () => Promise<{
106
+ email: string;
107
+ role: string;
108
+ name: string | null;
109
+ displayName: string | null;
110
+ avatarUrl: string | null;
111
+ }>;
112
+ loadTeam: () => Promise<TeamState>;
113
+ invite: (input: {
114
+ email: string;
115
+ role: "owner" | "editor";
116
+ }) => Promise<ActionResult>;
117
+ changeRole: (input: {
118
+ userId: string;
119
+ role: "owner" | "editor";
120
+ }) => Promise<ActionResult>;
121
+ resend: (input: {
122
+ email: string;
123
+ }) => Promise<ActionResult>;
124
+ remove: (input: {
125
+ userId: string;
126
+ }) => Promise<ActionResult>;
127
+ }
128
+ declare const ACTION_NAMES: readonly ["publishContent", "removeContent", "mergeContentPr", "discardChange", "previewBody", "saveProfile", "saveAvatar", "changePassword", "loadBranding", "saveBranding", "saveBrandingPicture", "loadMe", "loadTeam", "invite", "changeRole", "resend", "remove"];
129
+ /** One row of a content list, as the site page loads it from Studio.readers.listItems. */
130
+ interface ListRow {
131
+ slug: string;
132
+ title: string;
133
+ date: string | null;
134
+ needsAttention: boolean;
135
+ isNew: boolean;
136
+ /** Where the live item is on the public site, or null when the type has no page. */
137
+ liveUrl: string | null;
138
+ pending: {
139
+ number: number;
140
+ state: "checking" | "ready" | "failed";
141
+ removal: boolean;
142
+ } | null;
143
+ }
144
+ type PendingSummary = {
145
+ number: number;
146
+ branch: string;
147
+ state: "checking" | "ready" | "failed";
148
+ failingCheck: string | null;
149
+ removal: boolean;
150
+ };
151
+
152
+ interface StudioContextValue {
153
+ contentTypes: Record<string, ContentTypeEntry>;
154
+ actions: StudioActions;
155
+ /** Who to ask when something needs the agency — "your web team" by default. */
156
+ supportName: string;
157
+ siteName: string;
158
+ }
159
+ declare function StudioProvider({ contentTypes, actions, supportName, siteName, children }: {
160
+ contentTypes: Record<string, ContentTypeEntry>;
161
+ actions: StudioActions;
162
+ supportName?: string;
163
+ siteName?: string;
164
+ children: ReactNode;
165
+ }): react.JSX.Element;
166
+ declare function useStudio(): StudioContextValue;
167
+ declare function useEntry(typeId: string): ContentTypeEntry | null;
168
+ /** Pictures for a type live under public/<last folder segment>/. */
169
+ declare function publicDirOf(entry: {
170
+ folder: string;
171
+ }): string;
172
+
173
+ /**
174
+ * The frame every Studio page sits in: the shell with the current path for the
175
+ * active nav item, plus Studio's page styles injected once. Session, nav and
176
+ * branding arrive as props from the site's server layout.
177
+ */
178
+ declare function StudioShellFrame({ siteName, logoUrl, user, role, nav, pinnedNav, branding, children }: {
179
+ siteName: string;
180
+ logoUrl?: string | null;
181
+ user: ShellUser | null;
182
+ role: string | null;
183
+ nav: NavItem[];
184
+ pinnedNav?: NavItem[];
185
+ branding?: {
186
+ name?: string | null;
187
+ logoUrl?: string | null;
188
+ } | null;
189
+ children: React.ReactNode;
190
+ }): react.JSX.Element;
191
+
192
+ declare function StudioNotConfigured(): react.JSX.Element;
193
+ declare function StudioNoAccess({ actual }: {
194
+ actual: string | null;
195
+ }): react.JSX.Element;
196
+
197
+ /** Home: one card per content type. `cards` is [{ typeId, count, waiting }] in registry order. */
198
+ declare function StudioDashboard({ greeting, cards }: {
199
+ greeting: string;
200
+ cards: {
201
+ typeId: string;
202
+ count: number;
203
+ waiting: number;
204
+ }[];
205
+ }): react.JSX.Element;
206
+ declare function StudioList({ typeId, items }: {
207
+ typeId: string;
208
+ items: ListRow[];
209
+ }): react.JSX.Element | null;
210
+ declare function StudioNew({ typeId, initialValues }: {
211
+ typeId: string;
212
+ initialValues: Record<string, unknown>;
213
+ }): react.JSX.Element | null;
214
+ declare function StudioEdit({ typeId, slug, frontmatter, body, pending }: {
215
+ typeId: string;
216
+ slug: string;
217
+ frontmatter: Record<string, unknown>;
218
+ body: string;
219
+ pending: PendingSummary | null;
220
+ }): react.JSX.Element | null;
221
+ declare function StudioPullRequest({ pr, status, item }: {
222
+ pr: {
223
+ number: number;
224
+ title: string;
225
+ html_url: string;
226
+ merged: boolean;
227
+ state: "open" | "closed";
228
+ branch: string;
229
+ removal: boolean;
230
+ mergeCommitSha: string | null;
231
+ };
232
+ status: PullRequestStatus | null;
233
+ item: {
234
+ noun: string;
235
+ label: string;
236
+ typeId: string;
237
+ slug: string;
238
+ liveUrl: string | null;
239
+ } | null;
240
+ }): react.JSX.Element;
241
+ declare function StudioHelp(): react.JSX.Element;
242
+ declare function StudioSignIn({ url, anonKey, redirectTo, siteName, error }: {
243
+ url: string;
244
+ anonKey: string;
245
+ redirectTo: string;
246
+ siteName: string;
247
+ error?: string | null;
248
+ }): react.JSX.Element;
249
+ declare function StudioAcceptInvite({ url, anonKey }: {
250
+ url: string;
251
+ anonKey: string;
252
+ }): react.JSX.Element;
253
+
254
+ /** Account — My Profile · Change Password · Team · Business. Data is loaded by the site page; the tabs are client forms. */
255
+ declare function StudioAccount({ me, manages, team, branding, myUserId, myRole }: {
256
+ me: {
257
+ email: string;
258
+ name: string | null;
259
+ displayName: string | null;
260
+ avatarUrl: string | null;
261
+ };
262
+ manages: boolean;
263
+ team: {
264
+ ok: true;
265
+ members: SiteMember[];
266
+ canInvite: boolean;
267
+ } | {
268
+ ok: false;
269
+ error: string;
270
+ } | null;
271
+ branding: SiteBranding | null;
272
+ myUserId: string;
273
+ myRole: string;
274
+ }): react.JSX.Element;
275
+
276
+ /**
277
+ * Client wrapper for any registered content type: ContentForm (client) →
278
+ * publishContent (server action). The registry entry holds functions and a
279
+ * zod schema, so it can't cross the server→client boundary — the page passes
280
+ * the type id and this component looks the entry up on the client.
281
+ */
282
+ declare function ContentEditor({ typeId, initialValues, initialBody, existing, isNew, footer }: {
283
+ typeId: string;
284
+ initialValues?: Record<string, unknown>;
285
+ /** A brand-new item: the web address follows the title until it's edited by hand. */
286
+ isNew?: boolean;
287
+ initialBody?: string;
288
+ /** An open change for this file; saves go onto its branch (05c). */
289
+ existing?: {
290
+ number: number;
291
+ branch: string;
292
+ state: "checking" | "ready" | "failed";
293
+ } | null;
294
+ /** Rendered under the fields, above the save bar (e.g. Remove this post). */
295
+ footer?: React.ReactNode;
296
+ }): react.JSX.Element;
297
+
298
+ /**
299
+ * Take an item down. Quiet link → inline confirm → one change that deletes the
300
+ * file, reviewed and published like any other. Nothing disappears until Publish.
301
+ */
302
+ declare function RemoveItem({ typeId, slug }: {
303
+ typeId: string;
304
+ slug: string;
305
+ }): react.JSX.Element | null;
306
+
307
+ /**
308
+ * Saved → Checking → Live, as a tracker, with one sentence of plain language
309
+ * under it and the Publish button that unlocks when the checks pass. Refreshes
310
+ * itself while checks are pending. Copy is load-bearing (see the brief).
311
+ */
312
+ declare function PublishPanel({ number, htmlUrl, merged, closed, branch, removal, mergedSha, status, item }: {
313
+ number: number;
314
+ htmlUrl: string;
315
+ merged: boolean;
316
+ /** Closed without publishing — discarded here or on GitHub. */
317
+ closed?: boolean;
318
+ branch: string;
319
+ /** The change deletes the post instead of editing it. */
320
+ removal?: boolean;
321
+ /** Merge commit once published; lets the page confirm the deploy rather than guess. */
322
+ mergedSha?: string | null;
323
+ status: PullRequestStatus | null;
324
+ /** The item this change is about, resolved by the site page (Studio.readers.locate); null when it can't be. */
325
+ item: {
326
+ noun: string;
327
+ label: string;
328
+ typeId: string;
329
+ slug: string;
330
+ liveUrl: string | null;
331
+ } | null;
332
+ }): react.JSX.Element;
333
+
334
+ declare function StudioTerm({ children, definition }: {
335
+ children: ReactNode;
336
+ definition: string;
337
+ }): react.JSX.Element;
338
+ /** Studio's jargon, defined once. */
339
+ declare const GLOSSARY: {
340
+ readonly pullRequest: "The safety step between you and the live website. When you save, your edit is set aside on GitHub (the service that stores your site) and the site is test-built with it first. Only when that passes can you press Publish and make it live. GitHub calls this a pull request — you never need to go there yourself.";
341
+ readonly repository: "Where your website actually lives: a private, backed-up folder on GitHub that keeps every version of every page. This list is read straight from it, so it's always current — even for items added outside Studio.";
342
+ readonly change: "Your edit while it's on its way to the live site. You save it, it gets checked, then you publish it. An item can have one change in progress at a time, and you can come back to it whenever you like.";
343
+ readonly checks: "A practice run. The whole website is rebuilt with your edit in it, to make sure nothing broke — a missing field, a picture that isn't there. If something's wrong you find out here, not on the live site.";
344
+ readonly publish: "Makes your checked edit live for everyone. Takes about two minutes to appear on the website.";
345
+ };
346
+ declare function Jargon({ term, children }: {
347
+ term: keyof typeof GLOSSARY;
348
+ children: ReactNode;
349
+ }): react.JSX.Element;
350
+
351
+ /**
352
+ * The words a list and editor use for a content type. Posts keep the exact
353
+ * strings Studio shipped with; any other type reads from its registry label
354
+ * and singular so a new type needs no new copy.
355
+ */
356
+ declare function copyFor(entry: Pick<ContentTypeEntry, "id" | "label" | "singular">): {
357
+ newItem: string;
358
+ empty: string;
359
+ first: string;
360
+ removeLink: string;
361
+ removeConfirm: string;
362
+ removing: string;
363
+ newSubtitle: string;
364
+ };
365
+
366
+ /** Studio's page styles (lists, account tabs, drop zones) — injected once by StudioShellFrame. */
367
+ declare const STUDIO_CSS = "\n/*\n * /admin \u2014 overrides only. Layout, colour and components come from\n * @realiizlabs/admin/shell (SHELL_CSS, injected by AdminShell). The Realiiz\n * palette is the package default, so this file just maps the shell's font\n * hooks onto the site's loaded fonts. To rebrand, redeclare tokens here:\n * .rz-admin { --signal: #0a58ca; }\n */\n.rz-admin {\n --rz-font: var(--font-inter), system-ui, sans-serif;\n --rz-font-display: var(--font-space-grotesk), system-ui, sans-serif;\n --rz-font-mono: var(--font-jetbrains-mono), ui-monospace, monospace;\n}\n\n/* Posts table: one wide column for the post (title, slug beneath in small mono) and\n tight fixed columns for the known-width bits. Nothing wraps; the title and slug end in\n an ellipsis with the full text as tooltip. The title opens the live post; the outbound\n arrow sits inline after it and appears on hover (the shell's a:hover .rz-arrow motion). */\n.rz-admin .rz-table--posts { table-layout: fixed; width: 100%; }\n.rz-admin .rz-table--posts td, .rz-admin .rz-table--posts th { white-space: nowrap; overflow: hidden; }\n.rz-admin .rz-col-title { width: auto; }\n.rz-admin .rz-col-date { width: 7.5rem; }\n.rz-admin .rz-col-status { width: 11.5rem; }\n.rz-admin .rz-col-actions { width: 8.5rem; }\n.rz-admin .rz-title-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }\n.rz-admin .rz-slug { font-family: var(--rz-font-mono); font-size: .78rem; margin-top: .2rem; }\n.rz-admin [data-slugs=\"off\"] .rz-slug { display: none; }\n.rz-admin .rz-toolbar { display: flex; justify-content: flex-end; margin: 0 0 .6rem; }\n.rz-admin .rz-switch { display: inline-flex; align-items: center; gap: .55rem; font-size: .8rem; color: var(--text-secondary); cursor: pointer; user-select: none; }\n.rz-admin .rz-switch input { position: absolute; opacity: 0; width: 1px; height: 1px; }\n.rz-admin .rz-switch__track { position: relative; width: 30px; height: 18px; border-radius: 999px; background: var(--surface-3); transition: background .15s; }\n.rz-admin .rz-switch__thumb { position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.25); transition: transform .15s; }\n.rz-admin .rz-switch input:checked + .rz-switch__track { background: var(--signal); }\n.rz-admin .rz-switch input:checked + .rz-switch__track .rz-switch__thumb { transform: translateX(12px); }\n.rz-admin .rz-switch input:focus-visible + .rz-switch__track { outline: 2px solid var(--signal); outline-offset: 2px; }\n.rz-admin .rz-switch:hover .rz-switch__label { color: var(--text-primary); }\n.rz-admin .rz-title-link { display: flex; align-items: center; gap: .35em; max-width: 100%; color: inherit; text-decoration: none; }\n.rz-admin .rz-title-link .rz-arrow { opacity: 0; }\n.rz-admin .rz-title-link:hover { color: var(--signal); }\n.rz-admin .rz-title-link:hover .rz-arrow { opacity: 1; }\n@media (max-width: 720px) {\n .rz-admin .rz-col-status { width: 9rem; }\n .rz-admin .rz-col-actions { width: 6rem; }\n}\n\n/* Dashboard card for a section that isn't built yet: the real card, faded, buttons disabled. */\n/* Account page (05b): tracker-style \u2014 H1, email, underlined tabs, stacked form cards. */\n.rz-admin .acct-h1 { font-size: 1.25rem; font-weight: 600; margin: 0 0 .25rem; color: var(--text-primary); }\n.rz-admin .acct-email { font-size: .875rem; color: var(--text-muted); margin: 0 0 1.25rem; }\n.rz-admin .acct-stack { display: flex; flex-direction: column; gap: 1rem; max-width: 560px; }\n.rz-admin .acct-stack:has(.acct-team) { max-width: none; }\n.rz-admin .acct-card { display: flex; flex-direction: column; gap: .9rem; }\n.rz-admin .acct-title { font-size: 1rem; font-weight: 600; margin: 0; color: var(--text-primary); }\n.rz-admin .acct-blurb { font-size: .875rem; color: var(--text-secondary); margin: -.4rem 0 0; line-height: 1.5; }\n.rz-admin .acct-field { display: flex; flex-direction: column; gap: .35rem; }\n.rz-admin .acct-label { font-size: .85rem; font-weight: 500; color: var(--text-primary); }\n.rz-admin .acct-hint { font-size: .78rem; color: var(--text-muted); }\n.rz-admin .acct-input { font: inherit; font-size: .9rem; color: var(--text-primary); background: var(--surface-0); border: 1px solid var(--border); border-radius: 6px; padding: 9px 11px; min-height: 40px; width: 100%; }\n.rz-admin .acct-input:read-only { color: var(--text-secondary); background: var(--surface-2); }\n.rz-admin .acct-input:focus-visible { outline: 2px solid var(--signal); outline-offset: 1px; }\n.rz-admin .acct-select { width: auto; padding-right: 2rem; }\n.rz-admin .acct-select--sm { min-height: 32px; padding: 4px 28px 4px 8px; font-size: .8rem; }\n.rz-admin .acct-actions { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }\n.rz-admin .acct-ok { font-size: .85rem; color: var(--signal); }\n.rz-admin .acct-link { font: inherit; font-size: .82rem; background: none; border: 0; padding: 0; color: var(--text-secondary); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }\n.rz-admin .acct-link:hover { color: var(--signal); }\n.rz-admin .acct-link--danger:hover { color: #c0392b; }\n.rz-admin .acct-link:disabled { opacity: .5; cursor: default; }\n.rz-admin .acct-team-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }\n.rz-admin .acct-invite__row { display: flex; gap: .6rem; align-items: center; flex-wrap: wrap; }\n.rz-admin .acct-invite__row .acct-input[type=\"email\"] { flex: 1 1 220px; width: auto; }\n.rz-admin .acct-person { display: flex; align-items: center; gap: .7rem; }\n.rz-admin .acct-person__avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--surface-3); border: 1px solid var(--border); display: inline-flex; align-items: center; justify-content: center; font-size: .7rem; font-weight: 600; overflow: hidden; flex-shrink: 0; }\n.rz-admin .acct-person__avatar img { width: 100%; height: 100%; object-fit: cover; }\n.rz-admin .acct-person__text { display: flex; flex-direction: column; line-height: 1.25; }\n.rz-admin .acct-person__email { font-size: .78rem; }\n.rz-admin .acct-row-actions { display: flex; gap: .8rem; align-items: center; white-space: nowrap; min-height: 34px; }\n.rz-admin .acct-help-list { margin: 0; padding-left: 1.2rem; color: var(--text-secondary); line-height: 1.7; }\n\n/* Sign-in: the password option under the magic-link form. */\n.rz-admin .pwsi-toggle { margin: 1.1rem 0 0; text-align: center; }\n.rz-admin .pwsi { display: flex; flex-direction: column; gap: .9rem; margin-top: 1.25rem; padding-top: 1.25rem; border-top: 1px solid var(--separator); }\n\n/* Password boxes: eye toggle + the requirement checklist that lights up as each rule passes. */\n.rz-admin .acct-pw { position: relative; }\n.rz-admin .acct-pw .acct-input { padding-right: 2.6rem; }\n.rz-admin .acct-pw__eye { position: absolute; right: .5rem; top: 50%; transform: translateY(-50%); background: none; border: 0; padding: .35rem; color: var(--text-muted); cursor: pointer; display: inline-flex; border-radius: 4px; }\n.rz-admin .acct-pw__eye:hover, .rz-admin .acct-pw__eye[aria-pressed=\"true\"] { color: var(--text-secondary); }\n.rz-admin .acct-rules { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .5rem; }\n.rz-admin .acct-rule { display: flex; align-items: center; gap: .5rem; font-size: .8125rem; color: var(--text-muted); transition: color .15s; }\n.rz-admin .acct-rule--met { color: var(--signal); }\n\n/* Picture uploader (avatar, logo, favicon): one grid everywhere \u2014 preview | buttons row, hint under the buttons. */\n.rz-admin .acct-pic { border: 1px dashed var(--border); border-radius: 8px; padding: 16px 18px; background: var(--surface-0); outline: none; transition: border-color .15s; }\n.rz-admin .acct-pic:focus-visible, .rz-admin .acct-pic--over { border-color: var(--signal); border-style: solid; }\n.rz-admin .acct-pic--busy { opacity: .7; }\n.rz-admin .acct-pic__file { display: none; }\n.rz-admin .acct-pic__empty { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; color: var(--text-muted); }\n.rz-admin .acct-pic__fallback { width: 44px; height: 44px; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; border-radius: 8px; background: var(--surface-3); color: var(--text-muted); font-size: .9rem; font-weight: 600; letter-spacing: .04em; }\n.rz-admin .acct-pic__fallback--circle { border-radius: 50%; }\n.rz-admin .acct-pic__or { font-size: .8125rem; }\n.rz-admin .acct-pic__has { display: flex; align-items: flex-start; gap: 16px; }\n.rz-admin .acct-pic__thumb { display: block; width: 96px; height: 96px; object-fit: cover; border-radius: 8px; border: 1px solid var(--border); background: var(--surface-3); flex-shrink: 0; }\n.rz-admin .acct-pic__thumb--circle { border-radius: 50%; }\n.rz-admin .acct-pic__meta { display: flex; flex-direction: column; gap: 10px; min-width: 0; }\n.rz-admin .acct-pic__actions { display: flex; align-items: center; gap: 14px; }\n.rz-admin .acct-pic__remove { font: inherit; font-size: .8125rem; background: none; border: 0; padding: 0; color: var(--text-muted); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }\n.rz-admin .acct-pic__remove:hover { color: #c0392b; }\n.rz-admin .acct-pic__remove:disabled { cursor: default; opacity: .6; }\n.rz-admin .acct-pic__hint { margin: 10px 0 0; font-size: .75rem; color: var(--text-muted); line-height: 1.4; }\n.rz-admin .acct-pic__has .acct-pic__hint { margin-top: 0; }\n.rz-admin .acct-pic__note { margin: 8px 0 0; }\n.rz-admin .acct-pic__error { margin: 8px 0 0; font-size: .8rem; color: #c0392b; }\n\n/* Branded dropdown (role pickers): a bordered button that opens the shell's menu, checkmark on the current choice. */\n.rz-admin .acct-dd { position: relative; display: inline-block; }\n.rz-admin .acct-dd__btn { display: inline-flex; align-items: center; gap: .5rem; font: inherit; font-size: .85rem; color: var(--text-primary); background: var(--surface-0); border: 1px solid var(--border); border-radius: 6px; padding: 0 .7rem; height: 34px; cursor: pointer; }\n.rz-admin .acct-dd__btn:hover { border-color: var(--text-muted); }\n.rz-admin .acct-dd__btn:focus-visible { outline: 2px solid var(--signal); outline-offset: 1px; }\n.rz-admin .acct-dd__btn:disabled { opacity: .6; cursor: default; }\n.rz-admin .acct-dd__menu { left: 0; right: auto; min-width: 15rem; padding: .3rem 0; }\n.rz-admin .acct-dd__item { align-items: flex-start; }\n.rz-admin .acct-dd__check { width: 1rem; flex-shrink: 0; color: var(--signal); font-weight: 700; }\n.rz-admin .acct-dd__text { display: flex; flex-direction: column; line-height: 1.3; }\n.rz-admin .acct-dd__hint { font-size: .74rem; color: var(--text-muted); }\n/* Team table: everything on one centre line, and the role menu may overflow the table (the shell\n clips .rz-table for rounded corners, so round the header cells by hand instead). */\n.rz-admin .acct-team { overflow: visible; }\n.rz-admin .acct-team thead th:first-child { border-top-left-radius: 8px; }\n.rz-admin .acct-team thead th:last-child { border-top-right-radius: 8px; }\n.rz-admin .acct-team tbody tr:last-child td { border-bottom: 0; }\n.rz-admin .acct-team td { vertical-align: middle; }\n.rz-admin .acct-team td > * { vertical-align: middle; }\n.rz-admin .acct-team .rz-pill { position: relative; top: 0; }\n.rz-admin .acct-dd__menu { z-index: 60; }\n.rz-admin .acct-invite__row .acct-dd__btn { height: 40px; }\n\n.rz-admin .rz-term { position: relative; display: inline; }\n.rz-admin .rz-term__word { cursor: help; text-decoration: underline dotted; text-underline-offset: 4px; text-decoration-color: color-mix(in srgb, currentColor 50%, transparent); }\n.rz-admin .rz-term__word:hover, .rz-admin .rz-term__word:focus-visible { text-decoration-color: var(--signal); outline: none; }\n.rz-admin .rz-term__tip { position: absolute; left: 0; top: calc(100% + 8px); z-index: 70; width: 272px; max-width: 80vw; padding: 12px 14px; border-radius: 8px; background: var(--text-primary); color: var(--surface-0); font-size: .8125rem; line-height: 1.4; font-weight: 400; box-shadow: 0 8px 24px rgba(14,23,38,.18); opacity: 0; pointer-events: none; transition: opacity .15s; }\n.rz-admin .rz-term__tip--open { opacity: 1; }\n";
368
+
369
+ /**
370
+ * Plain-English facts about how Studio works — the same text a site's help
371
+ * assistant reads. Generic: no site or agency names.
372
+ */
373
+ declare const STUDIO_FACTS = "\nHOW REALIIZ STUDIO WORKS (the publishing dashboard at /admin)\n\nThe big picture: your website is stored as files in a private \"repository\" on GitHub (think: a version-controlled folder in the cloud that keeps every change ever made). Studio is a friendly front end over that folder. You never need to open GitHub \u2014 every action you take in Studio is one of these steps:\n\n1. SAVE. You edit a post and click \"Save and continue\" (or \"Save changes\"). Studio writes your post as a file and opens a \"change\" \u2014 GitHub calls this a pull request, or PR. Nothing on the live site changes yet. Think of it as a draft submitted for checking.\n2. CHECKS. The moment a change exists, the site builds a private test copy with your change in it, to make sure nothing breaks (a broken link tag, a missing required field, an image path that doesn't exist). This usually takes a minute or two. The change page shows a tracker: Saved \u2192 Checks \u2192 Published \u2192 Live, and refreshes itself.\n3. PUBLISH. When the checks pass the status becomes \"Ready to publish\" and the Publish button unlocks. A \"Preview the change\" button also appears: it opens a private copy of the website with your edit in it, so you can see exactly how the post will look before anyone else does. Clicking Publish sends your change to the live site.\n4. LIVE. The website then rebuilds itself with your change \u2014 usually about two minutes. The Live step keeps spinning until the rebuild is really done, and \"View the post\" unlocks at that moment. If you look at the website before the Live step turns green you will still see the old version; that's normal, not a failure.\n\nCONTENT TYPES: the sidebar lists every kind of content this site publishes \u2014 Blog posts and Speaking & Media (talks, panels, podcasts; shown at realiiz.com/speaking, upcoming first). Speaking items have a date, optional start/end time and time zone, a kind, a host, a place, links for details, the recording and slides, and \u2014 for Ed's own events \u2014 an RSVP link, capacity, \u201Csign-ups closed\u201D and \u201Cprivate\u201D (invited guests only; the event still shows but has no RSVP button). Each works the same way \u2014 a list, a New button, a form, Save, checks, Publish.\n\nSTATUSES you'll see in each list and on each item:\n- Live: what's on the website right now, no change waiting.\n- Checking\u2026: you saved; the test build is running. Wait a minute or two.\n- Ready to publish: checks passed; open the change and click Publish.\n- Needs fixing: the test build failed. Nothing was published. Open the post, fix the problem the message names, save again \u2014 the same change re-runs its checks.\n- New \u00B7 \u2026: a brand-new post that exists only in a change; it isn't on the live site until you publish.\n\nRULES OF THUMB:\n- One open change per post. Saving again while a change is waiting updates that same change (it doesn't create a second one). The sticky bar at the bottom of the editor tells you which change you're updating.\n- You can leave and come back: the posts table's Status column links to the change, and the post page shows a notice with a Publish button when a change is waiting.\n- Changed your mind? On the change page, \"Discard this change\" (with a confirm step) throws the unpublished edits away. The live site is untouched and the post goes back to Live.\n- \"View on GitHub\" opens the raw change for the curious; you never need it.\n- Only signed-in editors and owners can save or publish. Sign-in is by email link (no password).\n- If two people edit the same post, the second to publish is told the file changed first and asked to redo their edit on the latest version \u2014 nothing is silently overwritten.\n\nFIELDS on a blog post:\n- Title (H1): the headline at the top of the post. Can be long.\n- Web address (slug): the last part of the post's URL, e.g. my-first-post \u2192 realiiz.com/blog/my-first-post. Lowercase words joined by hyphens.\n- Excerpt: the short teaser shown on the blog listing page.\n- Publish date / Last updated: shown on the post and used for ordering; Last updated is optional, for revisions.\n- Author, Tags (exactly 3, lowercase-hyphenated), Header image (choose or drop a picture \u2014 see PICTURES below), Header image description (alt text for accessibility), Featured (pins the post).\n- Search & social group: Search title (what Google and the browser tab show \u2014 aim for 50\u201360 characters including the site name; blank means same as Title), Meta description (the snippet under the title in Google \u2014 aim for 120\u2013155 characters), Focus keyword (the phrase the post targets; used for SEO reporting, never shown), Social share image (the picture for social cards; blank reuses the hero image), Canonical (advanced; leave blank unless the article was published elsewhere first).\n- Post content: Markdown. The toolbar buttons toggle formatting on and off; Preview shows exactly how the blog will render it; the tooltip button wraps a selected term in a definition that appears on hover; Expand fills the window (Esc to come back).\n- Length gauges under the search fields: the green band is the target range; the fill turns green when you're inside it and red when you're over.\n\nPICTURES:\n- Choose a picture (or drag one in, or paste) for the header image, the social share image, or anywhere in the post content via the toolbar's picture button. JPG, PNG or WebP up to 10 MB; Studio shrinks it in your browser to a web-friendly WebP (about 1600 pixels wide, usually 100\u2013400 KB) before sending.\n- Nothing is uploaded when you pick \u2014 the pictures are saved together with the post when you press Save, into the same change, and go live with it when you Publish. Discarding a change discards its pictures too.\n- Pictures are named after the post's web address (slug), so fill that in first: the header image becomes /blog/<slug>.webp and pictures in the text go under /blog/<slug>/.\n- Preview shows new pictures before they're saved. The \"Preview the change\" link after the checks shows them on the real site design.\n\nACCOUNT (avatar menu \u2192 Account, or Settings in the sidebar):\n- My Profile: your photo, your name (shown in the greeting and to teammates) and \u201CName on blog posts\u201D \u2014 how you're credited as the author; blank means your full name. Your email is your login and can't be changed here \u2014 ask your web team.\n- Change Password: optional. Email links always work; a password is a second way in. At least 10 characters. On the sign-in page, \"Have a password? Sign in with it instead\" reveals the password form. Forgot your password? There's nothing to reset \u2014 sign in with an email link and set a new one under Settings \u2192 Change Password.\n- Team (owners only): who can sign in to this site. \"+ Invite someone\" sends an email link \u2014 no password needed. Editors write and publish; owners also manage the team and the business details. Pending invites can be resent or revoked; active people can be removed from this site (their login survives for any other site). A site always keeps at least one owner. Support staff aren't listed in a site's team \u2014 they can reach every site without being a member.\n- Business (owners only): your business name, contact email/phone, logo and favicon. Saving puts your name and logo at the top of Studio for everyone who signs in to your site \u2014 it never changes the public website.\n- Help (avatar menu): the help centre. For now it points here, to the Ask bubble; a full browsable guide is coming.\n";
374
+
375
+ export { ACTION_NAMES, type ActionResult, ContentEditor, type DiscardOutcome, GLOSSARY, type ImagePayload, Jargon, type ListRow, type MergeOutcome, type PendingSummary, type PublishInput, PublishPanel, RemoveItem, STUDIO_CSS, STUDIO_FACTS, StudioAcceptInvite, StudioAccount, type StudioActions, type StudioContextValue, StudioDashboard, StudioEdit, StudioHelp, StudioList, StudioNew, StudioNoAccess, StudioNotConfigured, StudioProvider, StudioPullRequest, StudioShellFrame, StudioSignIn, StudioTerm, type TeamState, copyFor, publicDirOf, useEntry, useStudio };