@odla-ai/chapter 0.40.1 → 0.40.3

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.
package/README.md CHANGED
@@ -546,9 +546,11 @@ builderLibrary: {
546
546
 
547
547
  Every resource has exactly one scope: `{ kind: "chapter", chapterId }`,
548
548
  `{ kind: "built_not_found" }`, or `{ kind: "global" }`. There are no groups or
549
- audience arrays. A Chapter can author only its own scope. Its admins and agents
550
- see its own published material plus global material, never a sibling Chapter or
551
- Built Not Found-only material. Built Not Found admins see every configured
549
+ audience arrays. A Chapter can author only its own scope. Built Not Found can
550
+ author material for itself, every Chapter, or exactly one Chapter from its
551
+ configured source list. A Chapter's admins and agents see its own published
552
+ material plus global material, never a sibling Chapter or Built Not Found-only
553
+ material. Built Not Found admins see every configured
552
554
  Chapter source and can promote a Chapter resource to a global copy; the server
553
555
  records the source Chapter, source resource/document revisions, actor, and time.
554
556
 
@@ -556,7 +558,10 @@ Resources are bounded documents/notes, HTTPS links/inspiration, or canonical
556
558
  Brand `{ bookId, assetId }` references. Asset URLs, bytes, storage credentials,
557
559
  HTML, and executable content are not part of the contract. The standard admin
558
560
  adds a visibly private **Builder Library** tab separate from **Member content**.
559
- Saving a library item never creates or changes a member page.
561
+ It is a compact CMS inventory with search and type/status filters, type-specific
562
+ add/edit fields, previews, explicit Draft/Published/Archived state, and a
563
+ visibility picker whose Chapter choices come only from checked-in network
564
+ configuration. Saving a library item never creates or changes a member page.
560
565
 
561
566
  An authenticated admin or site-building agent reads the bounded, published-only
562
567
  manifest from `/api/admin/builder-library/manifest`. On Built Not Found, pass a
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-IAWBE5PX.js";
8
8
 
9
9
  // src/ui/admin.tsx
10
- import { useEffect as useEffect11, useMemo as useMemo2, useRef, useState as useState17 } from "preact/hooks";
10
+ import { useEffect as useEffect11, useMemo as useMemo3, useRef, useState as useState17 } from "preact/hooks";
11
11
  import { ClerkGate, SignedIn, SignedOut, SignIn, useClerkAuth } from "@odla-ai/auth-clerk";
12
12
  import { CrmClient } from "@odla-ai/crm";
13
13
 
@@ -1688,37 +1688,101 @@ function availabilitySection(options = {}) {
1688
1688
  }
1689
1689
 
1690
1690
  // src/ui/admin-builder-library.tsx
1691
- import { useEffect as useEffect8, useState as useState13 } from "preact/hooks";
1691
+ import { useEffect as useEffect8, useMemo as useMemo2, useState as useState13 } from "preact/hooks";
1692
1692
  import { jsx as jsx18, jsxs as jsxs15 } from "preact/jsx-runtime";
1693
- var emptyDraft = () => ({ resources: [] });
1694
- var scopeLabel = (resource) => resource.scope.kind === "chapter" ? `Only ${resource.scope.chapterId}` : resource.scope.kind === "built_not_found" ? "Only Built Not Found" : "Every chapter";
1693
+ var KINDS = ["document", "note", "link", "inspiration", "image", "file"];
1694
+ var KIND_LABEL = { document: "Document", note: "Note", link: "Link", inspiration: "Inspiration", image: "Image", file: "File" };
1695
+ var STATUS_LABEL = { draft: "Draft", published: "Published", archived: "Archived" };
1696
+ var GRID = { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 360px), 1fr))", gap: 16, alignItems: "start" };
1697
+ var STACK = { display: "flex", flexDirection: "column", gap: 10 };
1698
+ var ROW = { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" };
1699
+ var META = { display: "flex", gap: 6, alignItems: "center", flexWrap: "wrap", color: "var(--ui-text-faint)", fontSize: 13 };
1700
+ var ITEM = { width: "100%", textAlign: "left", padding: 12, borderRadius: 10, border: "1px solid var(--ui-border)", background: "var(--ui-surface)", color: "inherit", cursor: "pointer" };
1701
+ var emptyDocument = () => ({ resources: [] });
1702
+ var localKey = (id) => `local:${id}`;
1703
+ var sourceKey = (sourceId, id) => `source:${sourceId}:${id}`;
1704
+ var cleanId = (value) => value.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 80);
1705
+ var scopeLabel = (scope) => scope.kind === "chapter" ? `Only ${scope.chapterId}` : scope.kind === "built_not_found" ? "Built Not Found only" : "All chapters";
1706
+ var contentSummary = (item) => item.summary || item.bodyMarkdown?.replace(/[#*_>`-]/g, "").trim().slice(0, 140) || item.url || item.brandAsset?.filename || item.brandAsset?.assetId || "No description yet";
1707
+ function localResource(kind, chapterId, mode) {
1708
+ const suffix = Date.now().toString(36);
1709
+ return {
1710
+ id: `untitled-${kind}-${suffix}`,
1711
+ kind,
1712
+ title: `Untitled ${KIND_LABEL[kind].toLowerCase()}`,
1713
+ tags: [],
1714
+ scope: mode === "hub" ? { kind: "built_not_found" } : { kind: "chapter", chapterId },
1715
+ status: "draft",
1716
+ revision: 1,
1717
+ ...kind === "document" || kind === "note" ? { bodyMarkdown: "Start writing\u2026" } : {},
1718
+ ...kind === "link" || kind === "inspiration" ? { url: "https://example.com" } : {},
1719
+ ...kind === "image" || kind === "file" ? { brandAsset: { bookId: "brand-book", assetId: `replace-${suffix}` } } : {},
1720
+ provenance: { createdBy: "server", createdAt: 0, updatedBy: "server", updatedAt: 0 }
1721
+ };
1722
+ }
1723
+ function ResourcePreview({ item }) {
1724
+ return /* @__PURE__ */ jsxs15("div", { className: "card", style: { padding: 14 }, children: [
1725
+ /* @__PURE__ */ jsx18("div", { className: "card-label", children: "Preview" }),
1726
+ /* @__PURE__ */ jsx18("h3", { style: { margin: "6px 0" }, children: item.title || "Untitled" }),
1727
+ item.summary ? /* @__PURE__ */ jsx18("p", { style: { margin: "0 0 10px" }, children: item.summary }) : null,
1728
+ item.bodyMarkdown ? /* @__PURE__ */ jsx18("pre", { style: { margin: 0, whiteSpace: "pre-wrap", font: "inherit", lineHeight: 1.55 }, children: item.bodyMarkdown }) : null,
1729
+ item.url ? /* @__PURE__ */ jsx18("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.url }) : null,
1730
+ item.brandAsset ? /* @__PURE__ */ jsxs15("div", { style: META, children: [
1731
+ "Brand asset \xB7 ",
1732
+ item.brandAsset.bookId,
1733
+ " / ",
1734
+ item.brandAsset.assetId,
1735
+ item.brandAsset.filename ? ` \xB7 ${item.brandAsset.filename}` : ""
1736
+ ] }) : null
1737
+ ] });
1738
+ }
1695
1739
  function BuilderLibraryAdmin({ ctx, initialData }) {
1696
- const resource = useAdminResource(ctx.getToken, "/api/admin/builder-library");
1697
- const [draft, setDraft] = useState13(JSON.stringify(initialData?.local?.document ?? emptyDraft(), null, 2));
1740
+ const remote = useAdminResource(ctx.getToken, "/api/admin/builder-library");
1741
+ const seed = initialData ?? remote.data;
1742
+ const [resources, setResources] = useState13(() => [...seed?.local?.document.resources ?? []]);
1743
+ const [selectedKey, setSelectedKey] = useState13(() => seed?.local?.document.resources[0]?.id ? localKey(seed.local.document.resources[0].id) : null);
1744
+ const [query, setQuery] = useState13("");
1745
+ const [kindFilter, setKindFilter] = useState13("all");
1746
+ const [statusFilter, setStatusFilter] = useState13("all");
1747
+ const [newKind, setNewKind] = useState13("document");
1698
1748
  const [saveError, setSaveError] = useState13(null);
1699
1749
  const [saving, setSaving] = useState13(false);
1700
1750
  useEffect8(() => {
1701
- if (resource.data) setDraft(JSON.stringify(resource.data.local?.document ?? emptyDraft(), null, 2));
1702
- }, [resource.data]);
1703
- const data = initialData ?? resource.data;
1704
- if (!data && resource.loading) return /* @__PURE__ */ jsx18(AdminPage, { children: /* @__PURE__ */ jsx18(AdminNote, { children: "Loading Builder Library\u2026" }) });
1705
- if (resource.error || !data) return /* @__PURE__ */ jsx18(AdminPage, { children: /* @__PURE__ */ jsxs15(AdminNote, { children: [
1751
+ if (!remote.data) return;
1752
+ const next = [...remote.data.local?.document.resources ?? []];
1753
+ setResources(next);
1754
+ setSelectedKey((current) => current && next.some((item) => localKey(item.id) === current) ? current : next[0] ? localKey(next[0].id) : null);
1755
+ }, [remote.data]);
1756
+ const data = initialData ?? remote.data;
1757
+ const entries = useMemo2(() => data ? [
1758
+ ...resources.map((item) => ({ key: localKey(item.id), resource: item, local: true, sourceId: data.mode === "hub" ? "Built Not Found" : "This chapter", sourceRevision: data.local?.revision ?? 0 })),
1759
+ ...data.sources.flatMap((source) => source.document.resources.map((item) => ({ key: sourceKey(source.sourceId, item.id), resource: item, local: false, sourceId: source.sourceId, sourceRevision: source.revision })))
1760
+ ] : [], [data, resources]);
1761
+ const visible = entries.filter(({ resource, sourceId }) => {
1762
+ const haystack = `${resource.title} ${resource.summary ?? ""} ${resource.tags.join(" ")} ${sourceId}`.toLowerCase();
1763
+ return (!query.trim() || haystack.includes(query.toLowerCase().trim())) && (kindFilter === "all" || resource.kind === kindFilter) && (statusFilter === "all" || resource.status === statusFilter);
1764
+ });
1765
+ const selected = entries.find((entry) => entry.key === selectedKey) ?? visible[0] ?? null;
1766
+ if (!data && remote.loading) return /* @__PURE__ */ jsx18(AdminPage, { children: /* @__PURE__ */ jsx18(AdminNote, { children: "Loading Builder Library\u2026" }) });
1767
+ if (remote.error || !data) return /* @__PURE__ */ jsx18(AdminPage, { children: /* @__PURE__ */ jsxs15(AdminNote, { children: [
1706
1768
  "Builder Library could not be loaded: ",
1707
- resource.error ?? "unknown error"
1769
+ remote.error ?? "unknown error"
1708
1770
  ] }) });
1771
+ const update = (change) => {
1772
+ if (!selected?.local) return;
1773
+ setResources((items) => items.map((item) => item.id === selected.resource.id ? { ...item, ...change } : item));
1774
+ };
1775
+ const add = () => {
1776
+ const item = localResource(newKind, data.chapterTargets[0]?.chapterId ?? "chapter", data.mode);
1777
+ setResources((items) => [...items, item]);
1778
+ setSelectedKey(localKey(item.id));
1779
+ };
1709
1780
  const save = async () => {
1710
1781
  setSaveError(null);
1711
- let document2;
1712
- try {
1713
- document2 = JSON.parse(draft);
1714
- } catch (error) {
1715
- setSaveError([`JSON is invalid: ${error instanceof Error ? error.message : String(error)}`]);
1716
- return;
1717
- }
1718
1782
  setSaving(true);
1719
1783
  try {
1720
- await adminFetch(ctx.getToken, "/api/admin/builder-library", { method: "PUT", body: JSON.stringify({ expectedRevision: data.local?.revision ?? 0, document: document2 }) });
1721
- resource.refresh();
1784
+ await adminFetch(ctx.getToken, "/api/admin/builder-library", { method: "PUT", body: JSON.stringify({ expectedRevision: data.local?.revision ?? 0, document: { ...data.local?.document ?? emptyDocument(), resources } }) });
1785
+ remote.refresh();
1722
1786
  } catch (error) {
1723
1787
  const structured = error;
1724
1788
  setSaveError(structured.errors ?? [error instanceof Error ? error.message : String(error)]);
@@ -1726,79 +1790,163 @@ function BuilderLibraryAdmin({ ctx, initialData }) {
1726
1790
  setSaving(false);
1727
1791
  }
1728
1792
  };
1729
- const promote = async (chapterId, resourceId) => {
1793
+ const promote = async (entry) => {
1730
1794
  setSaveError(null);
1731
1795
  try {
1732
- await adminFetch(ctx.getToken, "/api/admin/builder-library/promote", { method: "POST", body: JSON.stringify({ chapterId, resourceId, expectedRevision: data.local?.revision ?? 0 }) });
1733
- resource.refresh();
1796
+ await adminFetch(ctx.getToken, "/api/admin/builder-library/promote", { method: "POST", body: JSON.stringify({ chapterId: entry.sourceId, resourceId: entry.resource.id, expectedRevision: data.local?.revision ?? 0 }) });
1797
+ remote.refresh();
1734
1798
  } catch (error) {
1735
1799
  setSaveError([error instanceof Error ? error.message : String(error)]);
1736
1800
  }
1737
1801
  };
1802
+ const scopeValue = selected?.resource.scope.kind === "chapter" ? `chapter:${selected.resource.scope.chapterId}` : selected?.resource.scope.kind ?? "built_not_found";
1738
1803
  return /* @__PURE__ */ jsxs15(AdminPage, { children: [
1739
- /* @__PURE__ */ jsxs15(Panel, { children: [
1740
- /* @__PURE__ */ jsx18("div", { className: "card-label", children: "Builder Library \xB7 private" }),
1741
- /* @__PURE__ */ jsx18("h2", { children: "References for the agents building sites" }),
1742
- /* @__PURE__ */ jsx18("p", { children: "This is not member content. Saving a document, Brand asset reference, link, or inspiration here never publishes it to a member page." }),
1743
- /* @__PURE__ */ jsxs15("div", { className: "stat-band", children: [
1744
- /* @__PURE__ */ jsxs15("div", { children: [
1745
- /* @__PURE__ */ jsx18("strong", { children: data.local?.document.resources.length ?? 0 }),
1746
- /* @__PURE__ */ jsx18("span", { children: "Local resources" })
1804
+ /* @__PURE__ */ jsxs15(Panel, { title: /* @__PURE__ */ jsxs15("span", { children: [
1805
+ "Builder Library ",
1806
+ /* @__PURE__ */ jsx18("span", { className: "muted", children: "\xB7 private" })
1807
+ ] }), actions: /* @__PURE__ */ jsxs15("div", { style: ROW, children: [
1808
+ /* @__PURE__ */ jsx18("select", { "aria-label": "New resource type", value: newKind, onChange: (event) => setNewKind(event.currentTarget.value), children: KINDS.map((kind) => /* @__PURE__ */ jsx18("option", { value: kind, children: KIND_LABEL[kind] }, kind)) }),
1809
+ /* @__PURE__ */ jsx18("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring, onClick: add, children: "Add resource" })
1810
+ ] }), children: [
1811
+ /* @__PURE__ */ jsx18("p", { className: "muted", style: { margin: 0 }, children: "Documents, links, and Brand assets used by site-building agents. Nothing here appears on a member page." }),
1812
+ /* @__PURE__ */ jsxs15("div", { style: ROW, children: [
1813
+ /* @__PURE__ */ jsx18("input", { "aria-label": "Search resources", placeholder: "Search resources", value: query, onInput: (event) => setQuery(event.currentTarget.value), style: { flex: "1 1 220px" } }),
1814
+ /* @__PURE__ */ jsxs15("select", { "aria-label": "Filter by type", value: kindFilter, onChange: (event) => setKindFilter(event.currentTarget.value), children: [
1815
+ /* @__PURE__ */ jsx18("option", { value: "all", children: "All types" }),
1816
+ KINDS.map((kind) => /* @__PURE__ */ jsx18("option", { value: kind, children: KIND_LABEL[kind] }, kind))
1747
1817
  ] }),
1748
- /* @__PURE__ */ jsxs15("div", { children: [
1749
- /* @__PURE__ */ jsx18("strong", { children: data.sources.reduce((count, source) => count + source.document.resources.length, 0) }),
1750
- /* @__PURE__ */ jsx18("span", { children: "Shared in" })
1818
+ /* @__PURE__ */ jsxs15("select", { "aria-label": "Filter by status", value: statusFilter, onChange: (event) => setStatusFilter(event.currentTarget.value), children: [
1819
+ /* @__PURE__ */ jsx18("option", { value: "all", children: "All statuses" }),
1820
+ /* @__PURE__ */ jsx18("option", { value: "draft", children: "Draft" }),
1821
+ /* @__PURE__ */ jsx18("option", { value: "published", children: "Published" }),
1822
+ /* @__PURE__ */ jsx18("option", { value: "archived", children: "Archived" })
1823
+ ] })
1824
+ ] })
1825
+ ] }),
1826
+ /* @__PURE__ */ jsxs15("div", { style: GRID, "data-builder-library-cms": true, children: [
1827
+ /* @__PURE__ */ jsx18(Panel, { title: `${visible.length} resource${visible.length === 1 ? "" : "s"}`, children: /* @__PURE__ */ jsx18("div", { style: { ...STACK, maxHeight: "70vh", overflow: "auto" }, children: visible.length === 0 ? /* @__PURE__ */ jsx18("p", { className: "muted", children: "No resources match these filters." }) : visible.map((entry) => /* @__PURE__ */ jsxs15("button", { type: "button", style: { ...ITEM, ...selected?.key === entry.key ? { borderColor: "var(--ui-accent)", boxShadow: "0 0 0 1px var(--ui-accent)" } : {} }, onClick: () => setSelectedKey(entry.key), children: [
1828
+ /* @__PURE__ */ jsxs15("div", { style: { ...ROW, justifyContent: "space-between" }, children: [
1829
+ /* @__PURE__ */ jsx18("strong", { children: entry.resource.title }),
1830
+ /* @__PURE__ */ jsx18("span", { className: "badge", children: KIND_LABEL[entry.resource.kind] })
1751
1831
  ] }),
1752
- /* @__PURE__ */ jsxs15("div", { children: [
1753
- /* @__PURE__ */ jsx18("strong", { children: "3" }),
1754
- /* @__PURE__ */ jsx18("span", { children: "Scopes only" })
1832
+ /* @__PURE__ */ jsx18("p", { className: "muted", style: { margin: "6px 0", lineHeight: 1.4 }, children: contentSummary(entry.resource) }),
1833
+ /* @__PURE__ */ jsxs15("div", { style: META, children: [
1834
+ /* @__PURE__ */ jsx18("span", { children: STATUS_LABEL[entry.resource.status] }),
1835
+ /* @__PURE__ */ jsx18("span", { children: "\xB7" }),
1836
+ /* @__PURE__ */ jsx18("span", { children: scopeLabel(entry.resource.scope) }),
1837
+ /* @__PURE__ */ jsx18("span", { children: "\xB7" }),
1838
+ /* @__PURE__ */ jsx18("span", { children: entry.sourceId })
1755
1839
  ] })
1756
- ] }),
1757
- /* @__PURE__ */ jsx18(AdminNote, { children: "Visibility is always one chapter, only Built Not Found, or every chapter. Chapters never see sibling libraries." })
1840
+ ] }, entry.key)) }) }),
1841
+ /* @__PURE__ */ jsxs15(Panel, { title: selected ? selected.resource.title : "Select a resource", actions: selected?.local ? /* @__PURE__ */ jsxs15("div", { style: ROW, children: [
1842
+ /* @__PURE__ */ jsx18("button", { className: "btn secondary", type: "button", disabled: saving, onClick: () => update({ status: selected.resource.status === "archived" ? "draft" : "archived" }), children: selected.resource.status === "archived" ? "Restore draft" : "Archive" }),
1843
+ /* @__PURE__ */ jsx18("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring || saving, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save changes" })
1844
+ ] }) : null, children: [
1845
+ !selected ? /* @__PURE__ */ jsx18("p", { className: "muted", children: "Add a resource or choose one from the list." }) : selected.local ? /* @__PURE__ */ jsx18(LocalEditor, { selected: selected.resource, data, scopeValue, saving, update, setResources, setSelectedKey }) : /* @__PURE__ */ jsxs15("div", { style: STACK, children: [
1846
+ /* @__PURE__ */ jsxs15("div", { style: META, children: [
1847
+ "Read only \xB7 supplied by ",
1848
+ selected.sourceId,
1849
+ " \xB7 source revision ",
1850
+ selected.sourceRevision
1851
+ ] }),
1852
+ /* @__PURE__ */ jsxs15("div", { children: [
1853
+ /* @__PURE__ */ jsx18("span", { className: "badge", children: STATUS_LABEL[selected.resource.status] }),
1854
+ " ",
1855
+ /* @__PURE__ */ jsx18("span", { className: "badge", children: scopeLabel(selected.resource.scope) })
1856
+ ] }),
1857
+ /* @__PURE__ */ jsx18(ResourcePreview, { item: selected.resource }),
1858
+ data.mode === "hub" && selected.resource.scope.kind === "chapter" ? /* @__PURE__ */ jsx18("button", { className: "btn secondary", type: "button", onClick: () => void promote(selected), children: "Copy to all chapters" }) : null
1859
+ ] }),
1860
+ saveError ? /* @__PURE__ */ jsxs15("div", { className: "notice danger", role: "alert", children: [
1861
+ /* @__PURE__ */ jsx18("strong", { children: "Fix these items before saving:" }),
1862
+ /* @__PURE__ */ jsx18("ul", { children: saveError.map((error) => /* @__PURE__ */ jsx18("li", { children: error }, error)) })
1863
+ ] }) : null
1864
+ ] })
1865
+ ] })
1866
+ ] });
1867
+ }
1868
+ function LocalEditor({ selected, data, scopeValue, update, setResources, setSelectedKey }) {
1869
+ return /* @__PURE__ */ jsxs15("div", { style: STACK, children: [
1870
+ /* @__PURE__ */ jsxs15("div", { style: META, children: [
1871
+ "Local \xB7 resource revision ",
1872
+ selected.revision,
1873
+ " \xB7 library revision ",
1874
+ data.local?.revision ?? 0
1758
1875
  ] }),
1759
- data.sources.map((source) => /* @__PURE__ */ jsxs15(Panel, { children: [
1760
- /* @__PURE__ */ jsxs15("div", { className: "card-label", children: [
1761
- data.mode === "hub" ? "Chapter material" : "Global material",
1762
- " \xB7 read only"
1876
+ /* @__PURE__ */ jsxs15("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: [
1877
+ /* @__PURE__ */ jsxs15("label", { className: "field", children: [
1878
+ /* @__PURE__ */ jsx18("span", { children: "Title" }),
1879
+ /* @__PURE__ */ jsx18("input", { value: selected.title, onInput: (event) => update({ title: event.currentTarget.value }) })
1763
1880
  ] }),
1764
- /* @__PURE__ */ jsxs15("h2", { children: [
1765
- source.sourceId,
1766
- " \xB7 revision ",
1767
- source.revision
1881
+ /* @__PURE__ */ jsxs15("label", { className: "field", children: [
1882
+ /* @__PURE__ */ jsx18("span", { children: "ID" }),
1883
+ /* @__PURE__ */ jsx18("input", { value: selected.id, onInput: (event) => {
1884
+ const id = cleanId(event.currentTarget.value);
1885
+ setResources((items) => items.map((item) => item.id === selected.id ? { ...item, id } : item));
1886
+ setSelectedKey(localKey(id));
1887
+ } })
1768
1888
  ] }),
1769
- source.document.resources.length === 0 ? /* @__PURE__ */ jsx18(AdminNote, { children: "No resources delivered." }) : /* @__PURE__ */ jsx18("div", { className: "data-list", children: source.document.resources.map((item) => /* @__PURE__ */ jsxs15("article", { className: "card", children: [
1770
- /* @__PURE__ */ jsx18("strong", { children: item.title }),
1771
- /* @__PURE__ */ jsxs15("div", { className: "meeting-note", children: [
1772
- item.kind,
1773
- " \xB7 ",
1774
- item.status,
1775
- " \xB7 ",
1776
- scopeLabel(item)
1777
- ] }),
1778
- item.summary ? /* @__PURE__ */ jsx18("p", { children: item.summary }) : null,
1779
- data.mode === "hub" && item.scope.kind === "chapter" ? /* @__PURE__ */ jsx18("button", { className: "btn", type: "button", onClick: () => void promote(source.sourceId, item.id), children: "Make global" }) : null
1780
- ] }, item.id)) })
1781
- ] }, source.sourceId)),
1782
- /* @__PURE__ */ jsxs15(Panel, { children: [
1783
- /* @__PURE__ */ jsxs15("div", { className: "card-label", children: [
1784
- data.mode === "hub" ? "Built Not Found" : "This chapter",
1785
- " \xB7 editable"
1889
+ /* @__PURE__ */ jsxs15("label", { className: "field", children: [
1890
+ /* @__PURE__ */ jsx18("span", { children: "Type" }),
1891
+ /* @__PURE__ */ jsx18("select", { value: selected.kind, onChange: (event) => {
1892
+ const kind = event.currentTarget.value;
1893
+ update({ kind, bodyMarkdown: kind === "document" || kind === "note" ? selected.bodyMarkdown ?? "Start writing\u2026" : void 0, url: kind === "link" || kind === "inspiration" ? selected.url ?? "https://example.com" : void 0, brandAsset: kind === "image" || kind === "file" ? selected.brandAsset ?? { bookId: "brand-book", assetId: "replace-me" } : void 0 });
1894
+ }, children: KINDS.map((kind) => /* @__PURE__ */ jsx18("option", { value: kind, children: KIND_LABEL[kind] }, kind)) })
1786
1895
  ] }),
1787
- /* @__PURE__ */ jsxs15("h2", { children: [
1788
- "Local library \xB7 revision ",
1789
- data.local?.revision ?? 0
1896
+ /* @__PURE__ */ jsxs15("label", { className: "field", children: [
1897
+ /* @__PURE__ */ jsx18("span", { children: "Status" }),
1898
+ /* @__PURE__ */ jsxs15("select", { value: selected.status, onChange: (event) => update({ status: event.currentTarget.value }), children: [
1899
+ /* @__PURE__ */ jsx18("option", { value: "draft", children: "Draft" }),
1900
+ /* @__PURE__ */ jsx18("option", { value: "published", children: "Published" }),
1901
+ /* @__PURE__ */ jsx18("option", { value: "archived", children: "Archived" })
1902
+ ] })
1790
1903
  ] }),
1791
- /* @__PURE__ */ jsx18("p", { className: "meeting-note", children: "Use documents/notes, HTTPS links/inspiration, or canonical Brand book and asset IDs. The server owns revisions, actor history, and timestamps." }),
1792
1904
  /* @__PURE__ */ jsxs15("label", { className: "field", children: [
1793
- /* @__PURE__ */ jsx18("span", { children: "Builder resources (JSON)" }),
1794
- /* @__PURE__ */ jsx18("textarea", { rows: 24, value: draft, disabled: !data.localAuthoring || saving, onInput: (event) => setDraft(event.currentTarget.value), spellcheck: false })
1905
+ /* @__PURE__ */ jsx18("span", { children: "Visible to" }),
1906
+ /* @__PURE__ */ jsxs15("select", { value: scopeValue, onChange: (event) => {
1907
+ const value = event.currentTarget.value;
1908
+ update({ scope: value === "global" ? { kind: "global" } : value === "built_not_found" ? { kind: "built_not_found" } : { kind: "chapter", chapterId: value.slice("chapter:".length) } });
1909
+ }, children: [
1910
+ data.mode === "hub" ? /* @__PURE__ */ jsx18("option", { value: "built_not_found", children: "Built Not Found only" }) : null,
1911
+ data.mode === "hub" ? /* @__PURE__ */ jsx18("option", { value: "global", children: "All chapters" }) : null,
1912
+ data.chapterTargets.map(({ chapterId }) => /* @__PURE__ */ jsxs15("option", { value: `chapter:${chapterId}`, children: [
1913
+ "Only ",
1914
+ chapterId
1915
+ ] }, chapterId))
1916
+ ] })
1795
1917
  ] }),
1796
- saveError ? /* @__PURE__ */ jsxs15("div", { className: "notice danger", role: "alert", children: [
1797
- /* @__PURE__ */ jsx18("strong", { children: "Fix these items before saving:" }),
1798
- /* @__PURE__ */ jsx18("ul", { children: saveError.map((error) => /* @__PURE__ */ jsx18("li", { children: error }, error)) })
1799
- ] }) : null,
1800
- /* @__PURE__ */ jsx18("button", { className: "btn primary", type: "button", disabled: !data.localAuthoring || saving, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save Builder Library" })
1801
- ] })
1918
+ /* @__PURE__ */ jsxs15("label", { className: "field", children: [
1919
+ /* @__PURE__ */ jsx18("span", { children: "Tags" }),
1920
+ /* @__PURE__ */ jsx18("input", { value: selected.tags.join(", "), placeholder: "brand, membership", onInput: (event) => update({ tags: event.currentTarget.value.split(",").map((tag) => tag.trim()).filter(Boolean) }) })
1921
+ ] })
1922
+ ] }),
1923
+ /* @__PURE__ */ jsxs15("label", { className: "field", children: [
1924
+ /* @__PURE__ */ jsx18("span", { children: "Summary" }),
1925
+ /* @__PURE__ */ jsx18("textarea", { rows: 2, value: selected.summary ?? "", onInput: (event) => update({ summary: event.currentTarget.value || void 0 }) })
1926
+ ] }),
1927
+ selected.kind === "document" || selected.kind === "note" ? /* @__PURE__ */ jsxs15("label", { className: "field", children: [
1928
+ /* @__PURE__ */ jsx18("span", { children: "Markdown" }),
1929
+ /* @__PURE__ */ jsx18("textarea", { rows: 12, value: selected.bodyMarkdown ?? "", onInput: (event) => update({ bodyMarkdown: event.currentTarget.value }) })
1930
+ ] }) : null,
1931
+ selected.kind === "link" || selected.kind === "inspiration" ? /* @__PURE__ */ jsxs15("label", { className: "field", children: [
1932
+ /* @__PURE__ */ jsx18("span", { children: "HTTPS URL" }),
1933
+ /* @__PURE__ */ jsx18("input", { type: "url", value: selected.url ?? "", onInput: (event) => update({ url: event.currentTarget.value }) })
1934
+ ] }) : null,
1935
+ selected.kind === "image" || selected.kind === "file" ? /* @__PURE__ */ jsxs15("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: [
1936
+ /* @__PURE__ */ jsxs15("label", { className: "field", children: [
1937
+ /* @__PURE__ */ jsx18("span", { children: "Brand book ID" }),
1938
+ /* @__PURE__ */ jsx18("input", { value: selected.brandAsset?.bookId ?? "", onInput: (event) => update({ brandAsset: { bookId: event.currentTarget.value, assetId: selected.brandAsset?.assetId ?? "", ...selected.brandAsset?.filename ? { filename: selected.brandAsset.filename } : {} } }) })
1939
+ ] }),
1940
+ /* @__PURE__ */ jsxs15("label", { className: "field", children: [
1941
+ /* @__PURE__ */ jsx18("span", { children: "Asset ID" }),
1942
+ /* @__PURE__ */ jsx18("input", { value: selected.brandAsset?.assetId ?? "", onInput: (event) => update({ brandAsset: { bookId: selected.brandAsset?.bookId ?? "", assetId: event.currentTarget.value, ...selected.brandAsset?.filename ? { filename: selected.brandAsset.filename } : {} } }) })
1943
+ ] }),
1944
+ /* @__PURE__ */ jsxs15("label", { className: "field", children: [
1945
+ /* @__PURE__ */ jsx18("span", { children: "Filename (optional)" }),
1946
+ /* @__PURE__ */ jsx18("input", { value: selected.brandAsset?.filename ?? "", onInput: (event) => update({ brandAsset: { bookId: selected.brandAsset?.bookId ?? "", assetId: selected.brandAsset?.assetId ?? "", ...event.currentTarget.value ? { filename: event.currentTarget.value } : {} } }) })
1947
+ ] })
1948
+ ] }) : null,
1949
+ /* @__PURE__ */ jsx18(ResourcePreview, { item: selected })
1802
1950
  ] });
1803
1951
  }
1804
1952
  function builderLibrarySection() {
@@ -1895,7 +2043,7 @@ function emailSection(options = {}) {
1895
2043
  // src/ui/admin-member-content.tsx
1896
2044
  import { useEffect as useEffect9, useState as useState15 } from "preact/hooks";
1897
2045
  import { jsx as jsx20, jsxs as jsxs17 } from "preact/jsx-runtime";
1898
- var emptyDraft2 = () => ({ pages: [], upsellOverrides: [] });
2046
+ var emptyDraft = () => ({ pages: [], upsellOverrides: [] });
1899
2047
  function accessLabel(page) {
1900
2048
  return page.audience.kind === "all-members" ? "All members" : `Tiers: ${page.audience.tierIds.join(", ")}`;
1901
2049
  }
@@ -1904,12 +2052,12 @@ function MemberContentAdmin({
1904
2052
  initialData
1905
2053
  }) {
1906
2054
  const resource = useAdminResource(ctx.getToken, "/api/admin/member-content");
1907
- const [draft, setDraft] = useState15(JSON.stringify(initialData?.chapter?.document ?? emptyDraft2(), null, 2));
2055
+ const [draft, setDraft] = useState15(JSON.stringify(initialData?.chapter?.document ?? emptyDraft(), null, 2));
1908
2056
  const [saveError, setSaveError] = useState15(null);
1909
2057
  const [saving, setSaving] = useState15(false);
1910
2058
  useEffect9(() => {
1911
2059
  if (!resource.data) return;
1912
- setDraft(JSON.stringify(resource.data.chapter?.document ?? emptyDraft2(), null, 2));
2060
+ setDraft(JSON.stringify(resource.data.chapter?.document ?? emptyDraft(), null, 2));
1913
2061
  }, [resource.data]);
1914
2062
  const loaded = initialData ?? resource.data;
1915
2063
  if (!loaded && resource.loading) return /* @__PURE__ */ jsx20(AdminPage, { children: /* @__PURE__ */ jsx20(AdminNote, { children: "Loading member content\u2026" }) });
@@ -2323,7 +2471,7 @@ function Authed(props) {
2323
2471
  renderWorkspaceNav
2324
2472
  } = props;
2325
2473
  const { getToken, signOut } = useClerkAuth();
2326
- const client = useMemo2(
2474
+ const client = useMemo3(
2327
2475
  () => new CrmClient({
2328
2476
  basePath: crmBasePath,
2329
2477
  headers: async () => {
@@ -2384,11 +2532,11 @@ function Authed(props) {
2384
2532
  }
2385
2533
  function ChapterAdmin(props) {
2386
2534
  const copy = props.chapter?.copy ?? DEFAULT_CHAPTER_COPY;
2387
- const defaults = useMemo2(
2535
+ const defaults = useMemo3(
2388
2536
  () => props.chapter ? defaultAdminWorkspaces(props.chapter) : [],
2389
2537
  [props.chapter]
2390
2538
  );
2391
- const workspaces = useMemo2(() => {
2539
+ const workspaces = useMemo3(() => {
2392
2540
  if (typeof props.workspaces === "function") return props.workspaces(defaults);
2393
2541
  return props.workspaces ?? props.sections ?? defaults;
2394
2542
  }, [props.workspaces, props.sections, defaults]);
@@ -2574,4 +2722,4 @@ export {
2574
2722
  defaultAdminSections,
2575
2723
  meetingsSection
2576
2724
  };
2577
- //# sourceMappingURL=chunk-4K2Q5JBC.js.map
2725
+ //# sourceMappingURL=chunk-O22RRPBT.js.map