@realiizlabs/admin 0.15.9 → 0.16.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.
@@ -67,6 +67,8 @@ interface PublishInput {
67
67
  branch: string;
68
68
  } | null;
69
69
  images?: ImagePayload[];
70
+ /** The slug this item was opened as (edits only) — lets a save that changes the file name rename rather than duplicate. */
71
+ replaces?: string | null;
70
72
  }
71
73
  /** The pending Studio update, as Studio.readers.studioUpdate() returns it (serialisable). */
72
74
  interface StudioUpdate {
@@ -172,21 +174,31 @@ type PendingSummary = {
172
174
  removal: boolean;
173
175
  };
174
176
 
177
+ /** Per type, per field — the choices for a reference field, e.g. { events: { venue: ["soundplex"] } }. */
178
+ type StudioOptions = Record<string, Record<string, string[]>>;
175
179
  interface StudioContextValue {
176
180
  contentTypes: Record<string, ContentTypeEntry>;
177
181
  actions: StudioActions;
178
182
  /** Who to ask when something needs the agency — "your web team" by default. */
179
183
  supportName: string;
180
184
  siteName: string;
185
+ /**
186
+ * Option lists the server resolved for this request (an event's venue is one of the
187
+ * site's venues). The registry is imported client-side and can't read the repo, so a
188
+ * layout computes these with Studio.readers.listSlugs and passes them here.
189
+ */
190
+ options: StudioOptions;
181
191
  }
182
- declare function StudioProvider({ contentTypes, actions, supportName, siteName, children }: {
192
+ declare function StudioProvider({ contentTypes, actions, supportName, siteName, options, children }: {
183
193
  contentTypes: Record<string, ContentTypeEntry>;
184
194
  actions: StudioActions;
185
195
  supportName?: string;
186
196
  siteName?: string;
197
+ options?: StudioOptions;
187
198
  children: ReactNode;
188
199
  }): react.JSX.Element;
189
200
  declare function useStudio(): StudioContextValue;
201
+ /** The registry entry, with any server-resolved option lists merged over the entry's own. */
190
202
  declare function useEntry(typeId: string): ContentTypeEntry | null;
191
203
  /** Pictures for a type live under public/<last folder segment>/. */
192
204
  declare function publicDirOf(entry: {
@@ -307,11 +319,13 @@ declare function StudioAccount({ me, manages, team, branding, myUserId, myRole,
307
319
  * zod schema, so it can't cross the server→client boundary — the page passes
308
320
  * the type id and this component looks the entry up on the client.
309
321
  */
310
- declare function ContentEditor({ typeId, initialValues, initialBody, existing, isNew, footer }: {
322
+ declare function ContentEditor({ typeId, initialValues, initialBody, existing, isNew, replaces, footer }: {
311
323
  typeId: string;
312
324
  initialValues?: Record<string, unknown>;
313
325
  /** A brand-new item: the web address follows the title until it's edited by hand. */
314
326
  isNew?: boolean;
327
+ /** The slug this item was opened as; lets a save that changes the file name rename rather than duplicate. */
328
+ replaces?: string | null;
315
329
  initialBody?: string;
316
330
  /** An open change for this file; saves go onto its branch (05c). */
317
331
  existing?: {
@@ -485,4 +499,4 @@ declare function searchHelp(query: string, articles?: HelpArticle[]): HelpHit[];
485
499
  /** Every article as plain text, section by section — what STUDIO_FACTS carries to the assistant. */
486
500
  declare function helpAsText(vars?: HelpVars): string;
487
501
 
488
- export { ACTION_NAMES, type ActionResult, ContentEditor, type DiscardOutcome, GLOSSARY, HELP_ARTICLES, HELP_SECTIONS, type HelpArticle, type HelpSection, type HelpSectionId, type ImagePayload, InstallTracker, type Installing, Jargon, type ListRow, type MergeOutcome, type NudgeOutcome, 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 StudioUpdate, type TeamState, UpdateBody, UpdateStrip, canSeeUpdates, copyFor, fillVars, helpAsText, joinTypes, publicDirOf, searchHelp, useEntry, useInstalling, useStudio };
502
+ export { ACTION_NAMES, type ActionResult, ContentEditor, type DiscardOutcome, GLOSSARY, HELP_ARTICLES, HELP_SECTIONS, type HelpArticle, type HelpSection, type HelpSectionId, type ImagePayload, InstallTracker, type Installing, Jargon, type ListRow, type MergeOutcome, type NudgeOutcome, type PendingSummary, type PublishInput, PublishPanel, RemoveItem, STUDIO_CSS, STUDIO_FACTS, StudioAcceptInvite, StudioAccount, type StudioActions, type StudioContextValue, StudioDashboard, StudioEdit, StudioHelp, StudioList, StudioNew, StudioNoAccess, StudioNotConfigured, type StudioOptions, StudioProvider, StudioPullRequest, StudioShellFrame, StudioSignIn, StudioTerm, type StudioUpdate, type TeamState, UpdateBody, UpdateStrip, canSeeUpdates, copyFor, fillVars, helpAsText, joinTypes, publicDirOf, searchHelp, useEntry, useInstalling, useStudio };
@@ -67,6 +67,8 @@ interface PublishInput {
67
67
  branch: string;
68
68
  } | null;
69
69
  images?: ImagePayload[];
70
+ /** The slug this item was opened as (edits only) — lets a save that changes the file name rename rather than duplicate. */
71
+ replaces?: string | null;
70
72
  }
71
73
  /** The pending Studio update, as Studio.readers.studioUpdate() returns it (serialisable). */
72
74
  interface StudioUpdate {
@@ -172,21 +174,31 @@ type PendingSummary = {
172
174
  removal: boolean;
173
175
  };
174
176
 
177
+ /** Per type, per field — the choices for a reference field, e.g. { events: { venue: ["soundplex"] } }. */
178
+ type StudioOptions = Record<string, Record<string, string[]>>;
175
179
  interface StudioContextValue {
176
180
  contentTypes: Record<string, ContentTypeEntry>;
177
181
  actions: StudioActions;
178
182
  /** Who to ask when something needs the agency — "your web team" by default. */
179
183
  supportName: string;
180
184
  siteName: string;
185
+ /**
186
+ * Option lists the server resolved for this request (an event's venue is one of the
187
+ * site's venues). The registry is imported client-side and can't read the repo, so a
188
+ * layout computes these with Studio.readers.listSlugs and passes them here.
189
+ */
190
+ options: StudioOptions;
181
191
  }
182
- declare function StudioProvider({ contentTypes, actions, supportName, siteName, children }: {
192
+ declare function StudioProvider({ contentTypes, actions, supportName, siteName, options, children }: {
183
193
  contentTypes: Record<string, ContentTypeEntry>;
184
194
  actions: StudioActions;
185
195
  supportName?: string;
186
196
  siteName?: string;
197
+ options?: StudioOptions;
187
198
  children: ReactNode;
188
199
  }): react.JSX.Element;
189
200
  declare function useStudio(): StudioContextValue;
201
+ /** The registry entry, with any server-resolved option lists merged over the entry's own. */
190
202
  declare function useEntry(typeId: string): ContentTypeEntry | null;
191
203
  /** Pictures for a type live under public/<last folder segment>/. */
192
204
  declare function publicDirOf(entry: {
@@ -307,11 +319,13 @@ declare function StudioAccount({ me, manages, team, branding, myUserId, myRole,
307
319
  * zod schema, so it can't cross the server→client boundary — the page passes
308
320
  * the type id and this component looks the entry up on the client.
309
321
  */
310
- declare function ContentEditor({ typeId, initialValues, initialBody, existing, isNew, footer }: {
322
+ declare function ContentEditor({ typeId, initialValues, initialBody, existing, isNew, replaces, footer }: {
311
323
  typeId: string;
312
324
  initialValues?: Record<string, unknown>;
313
325
  /** A brand-new item: the web address follows the title until it's edited by hand. */
314
326
  isNew?: boolean;
327
+ /** The slug this item was opened as; lets a save that changes the file name rename rather than duplicate. */
328
+ replaces?: string | null;
315
329
  initialBody?: string;
316
330
  /** An open change for this file; saves go onto its branch (05c). */
317
331
  existing?: {
@@ -485,4 +499,4 @@ declare function searchHelp(query: string, articles?: HelpArticle[]): HelpHit[];
485
499
  /** Every article as plain text, section by section — what STUDIO_FACTS carries to the assistant. */
486
500
  declare function helpAsText(vars?: HelpVars): string;
487
501
 
488
- export { ACTION_NAMES, type ActionResult, ContentEditor, type DiscardOutcome, GLOSSARY, HELP_ARTICLES, HELP_SECTIONS, type HelpArticle, type HelpSection, type HelpSectionId, type ImagePayload, InstallTracker, type Installing, Jargon, type ListRow, type MergeOutcome, type NudgeOutcome, 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 StudioUpdate, type TeamState, UpdateBody, UpdateStrip, canSeeUpdates, copyFor, fillVars, helpAsText, joinTypes, publicDirOf, searchHelp, useEntry, useInstalling, useStudio };
502
+ export { ACTION_NAMES, type ActionResult, ContentEditor, type DiscardOutcome, GLOSSARY, HELP_ARTICLES, HELP_SECTIONS, type HelpArticle, type HelpSection, type HelpSectionId, type ImagePayload, InstallTracker, type Installing, Jargon, type ListRow, type MergeOutcome, type NudgeOutcome, type PendingSummary, type PublishInput, PublishPanel, RemoveItem, STUDIO_CSS, STUDIO_FACTS, StudioAcceptInvite, StudioAccount, type StudioActions, type StudioContextValue, StudioDashboard, StudioEdit, StudioHelp, StudioList, StudioNew, StudioNoAccess, StudioNotConfigured, type StudioOptions, StudioProvider, StudioPullRequest, StudioShellFrame, StudioSignIn, StudioTerm, type StudioUpdate, type TeamState, UpdateBody, UpdateStrip, canSeeUpdates, copyFor, fillVars, helpAsText, joinTypes, publicDirOf, searchHelp, useEntry, useInstalling, useStudio };
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { createContext, useContext, useState, useEffect, useId, useTransition, Suspense, useMemo, useRef, useCallback, useSyncExternalStore, Fragment as Fragment$1 } from 'react';
2
+ import { createContext, useContext, useMemo, useState, useEffect, useId, useTransition, Suspense, useRef, useCallback, useSyncExternalStore, Fragment as Fragment$1 } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import { usePathname, useRouter, useSearchParams } from 'next/navigation';
5
5
  import { createBrowserClient } from '@supabase/ssr';
@@ -7,8 +7,8 @@ import Link3 from 'next/link';
7
7
 
8
8
  // src/studio-ui/context.tsx
9
9
  var Ctx = createContext(null);
10
- function StudioProvider({ contentTypes, actions, supportName = "your web team", siteName = "Studio", children }) {
11
- return /* @__PURE__ */ jsx(Ctx.Provider, { value: { contentTypes, actions, supportName, siteName }, children });
10
+ function StudioProvider({ contentTypes, actions, supportName = "your web team", siteName = "Studio", options = {}, children }) {
11
+ return /* @__PURE__ */ jsx(Ctx.Provider, { value: { contentTypes, actions, supportName, siteName, options }, children });
12
12
  }
13
13
  function useStudio() {
14
14
  const v = useContext(Ctx);
@@ -16,7 +16,10 @@ function useStudio() {
16
16
  return v;
17
17
  }
18
18
  function useEntry(typeId) {
19
- return useStudio().contentTypes[typeId] ?? null;
19
+ const { contentTypes, options } = useStudio();
20
+ const entry = contentTypes[typeId] ?? null;
21
+ const served = options[typeId];
22
+ return useMemo(() => entry && served ? { ...entry, options: { ...entry.options, ...served } } : entry, [entry, served]);
20
23
  }
21
24
  function publicDirOf(entry) {
22
25
  return `/${entry.folder.split("/").filter(Boolean).pop() ?? "images"}/`;
@@ -307,20 +310,22 @@ function AccountMenu({ user, role, theme, onTheme, signOutPath, accountHref, hel
307
310
  }
308
311
  function TopBar({ siteName, productName, logoUrl, homeHref, siteUrl, user, role, theme, onTheme, signOutPath, accountHref, helpHref, version }) {
309
312
  return /* @__PURE__ */ jsxs("header", { className: "rz-topbar", children: [
310
- /* @__PURE__ */ jsxs(
311
- "a",
312
- {
313
- href: siteUrl || homeHref,
314
- className: "rz-topbar__brand",
315
- ...siteUrl ? { target: "_blank", rel: "noreferrer", title: "Open the live website in a new tab" } : {},
316
- children: [
317
- logoUrl && /* @__PURE__ */ jsx("img", { src: logoUrl, alt: "", className: "rz-topbar__logo" }),
318
- /* @__PURE__ */ jsx("span", { children: siteName }),
319
- /* @__PURE__ */ jsx("span", { className: "rz-topbar__product", children: productName }),
320
- siteUrl && /* @__PURE__ */ jsx(OutboundArrow, {})
321
- ]
322
- }
323
- ),
313
+ /* @__PURE__ */ jsxs("div", { className: "rz-topbar__brand", children: [
314
+ /* @__PURE__ */ jsxs(
315
+ "a",
316
+ {
317
+ href: siteUrl || homeHref,
318
+ className: "rz-topbar__site",
319
+ ...siteUrl ? { target: "_blank", rel: "noreferrer", title: "Open the live website in a new tab" } : {},
320
+ children: [
321
+ logoUrl && /* @__PURE__ */ jsx("img", { src: logoUrl, alt: "", className: "rz-topbar__logo" }),
322
+ /* @__PURE__ */ jsx("span", { children: siteName }),
323
+ siteUrl && /* @__PURE__ */ jsx(OutboundArrow, {})
324
+ ]
325
+ }
326
+ ),
327
+ /* @__PURE__ */ jsx("span", { className: "rz-topbar__product", children: productName })
328
+ ] }),
324
329
  user && role && /* @__PURE__ */ jsxs("div", { className: "rz-topbar__right", children: [
325
330
  /* @__PURE__ */ jsxs("span", { className: "rz-topbar__greeting", "data-testid": "rz-greeting", children: [
326
331
  "Welcome back, ",
@@ -383,11 +388,14 @@ var SHELL_CSS = `
383
388
 
384
389
  /* ---- frame ---------------------------------------------------------- */
385
390
  .rz-topbar { height: var(--rz-topbar); display: flex; align-items: center; gap: 1rem; padding: 0 1.25rem; border-bottom: 1px solid var(--border); background: var(--surface-1); flex-shrink: 0; }
386
- .rz-topbar__brand { display: flex; align-items: center; gap: .6rem; font-weight: 600; font-size: .9375rem; text-decoration: none; }
391
+ .rz-topbar__brand { display: flex; align-items: center; gap: .6rem; font-weight: 600; font-size: .9375rem; }
392
+ .rz-topbar__site { display: inline-flex; align-items: center; gap: .6rem; color: inherit; text-decoration: none; }
393
+ .rz-topbar__site:hover { color: var(--signal); }
387
394
  .rz-topbar__logo { height: 22px; width: auto; display: block; }
388
395
  .rz-topbar__product { font-weight: 400; color: var(--text-secondary); }
389
- .rz-topbar__brand .rz-arrow { color: var(--text-muted); opacity: 0; transition: opacity .15s, transform .15s; }
390
- .rz-topbar__brand:hover .rz-arrow { opacity: 1; }
396
+ /* The arrow's width is reserved, so "Studio" never shifts when it fades in. */
397
+ .rz-topbar__site .rz-arrow { color: var(--text-muted); opacity: 0; margin-left: -.25rem; transition: opacity .15s, transform .15s; }
398
+ .rz-topbar__site:hover .rz-arrow { opacity: 1; color: var(--signal); }
391
399
  .rz-topbar__right { margin-left: auto; display: flex; align-items: center; gap: 1rem; }
392
400
  .rz-topbar__greeting { color: var(--text-secondary); font-size: .8rem; }
393
401
  .rz-body { display: flex; flex: 1; min-height: 0; }
@@ -733,7 +741,7 @@ function Tabs({ options, value, onChange, label = "Sections" }) {
733
741
  }
734
742
 
735
743
  // src/version.ts
736
- var ADMIN_VERSION = "0.15.9" ;
744
+ var ADMIN_VERSION = "0.16.0" ;
737
745
 
738
746
  // src/studio-ui/css.ts
739
747
  var STUDIO_CSS = `
@@ -3115,7 +3123,7 @@ function toBase64(blob) {
3115
3123
  r.readAsDataURL(blob);
3116
3124
  });
3117
3125
  }
3118
- function ContentEditor({ typeId, initialValues, initialBody, existing = null, isNew = false, footer }) {
3126
+ function ContentEditor({ typeId, initialValues, initialBody, existing = null, isNew = false, replaces = null, footer }) {
3119
3127
  const entry = useEntry(typeId);
3120
3128
  const { publishContent, previewBody } = useStudio().actions;
3121
3129
  const [error, setError] = useState(null);
@@ -3133,7 +3141,7 @@ function ContentEditor({ typeId, initialValues, initialBody, existing = null, is
3133
3141
  startTransition(async () => {
3134
3142
  setError(null);
3135
3143
  const images = await pictures.toPayload(s.images, s.body);
3136
- const result = await publishContent({ type: typeId, frontmatter: s.frontmatter, body: s.body, images, existing: existing ? { number: existing.number, branch: existing.branch } : null });
3144
+ const result = await publishContent({ type: typeId, frontmatter: s.frontmatter, body: s.body, images, existing: existing ? { number: existing.number, branch: existing.branch } : null, replaces });
3137
3145
  if (result && result.ok === false) setError(result.error);
3138
3146
  resolve();
3139
3147
  });
@@ -3671,6 +3679,7 @@ function StudioEdit({ typeId, slug, frontmatter, body, pending }) {
3671
3679
  typeId: entry.id,
3672
3680
  initialValues: frontmatter,
3673
3681
  initialBody: body,
3682
+ replaces: slug,
3674
3683
  existing: pending ? { number: pending.number, branch: pending.branch, state: pending.state } : null,
3675
3684
  footer: !pending ? /* @__PURE__ */ jsx(RemoveItem, { typeId: entry.id, slug }) : void 0
3676
3685
  }
@@ -4986,6 +4995,8 @@ The old listing shows "Rescheduled" with a link to the new date. The new one is
4986
4995
 
4987
4996
  You can, for small corrections. But once people have shared the link or put it in a calendar, a visible "moved" trail avoids no-shows on the wrong day.
4988
4997
 
4998
+ On sites where the date is part of the event's web address, changing the date moves the event to a new address and the old one stops working \u2014 Studio handles the move cleanly (one file, not two), but any link already shared points at nothing. That's the real reason to prefer **Moved to**.
4999
+
4989
5000
  ## After it's over
4990
5001
 
4991
5002
  Nothing to do. Past events move to the **Past** section automatically the day after they end.