@drawnagency/primitives 0.1.63 → 0.1.65

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.
@@ -5,4 +5,16 @@
5
5
  export declare class LastOwnerError extends Error {
6
6
  constructor(message?: string);
7
7
  }
8
+ /**
9
+ * Thrown when an authenticated operation cannot proceed because the underlying
10
+ * Supabase session is missing, expired, or its refresh token is stale/used.
11
+ * This is distinct from a never-authenticated request: the caller passed the
12
+ * middleware gate (e.g. via a still-valid app session cookie) but the Supabase
13
+ * session needed for the operation is dead. API routes should translate this to
14
+ * HTTP 401 so the UI prompts the user to request a fresh link, rather than
15
+ * surfacing an opaque 500.
16
+ */
17
+ export declare class SessionExpiredError extends Error {
18
+ constructor(message?: string);
19
+ }
8
20
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/auth/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,SAAiC;CAIrD"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/auth/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,SAAiC;CAIrD;AAED;;;;;;;;GAQG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,SAAyD;CAI7E"}
@@ -3,5 +3,5 @@ export type { UiCapabilities } from "./capabilities";
3
3
  export { deriveUiCapabilities } from "./capabilities";
4
4
  export { requireSessionSecret, isSameOriginRequest, safeNextPath } from "./security";
5
5
  export { signSessionToken, verifySessionToken, signAudienceToken, verifyAudienceToken, setSessionCookie, SESSION_COOKIE, AUDIENCE_COOKIE, SESSION_MAX_AGE_SECONDS, AUDIENCE_MAX_AGE_SECONDS } from "./cookies";
6
- export { LastOwnerError } from "./errors";
6
+ export { LastOwnerError, SessionExpiredError } from "./errors";
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,UAAU,EACV,WAAW,GACZ,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAC/M,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,UAAU,EACV,WAAW,GACZ,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEtD,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,cAAc,EAAE,eAAe,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAC/M,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC"}
@@ -4,13 +4,14 @@ import {
4
4
  LastOwnerError,
5
5
  SESSION_COOKIE,
6
6
  SESSION_MAX_AGE_SECONDS,
7
+ SessionExpiredError,
7
8
  deriveUiCapabilities,
8
9
  setSessionCookie,
9
10
  signAudienceToken,
10
11
  signSessionToken,
11
12
  verifyAudienceToken,
12
13
  verifySessionToken
13
- } from "../chunk-UNWNT52N.js";
14
+ } from "../chunk-EM5DFHDZ.js";
14
15
  import {
15
16
  isSameOriginRequest,
16
17
  requireSessionSecret,
@@ -24,6 +25,7 @@ export {
24
25
  LastOwnerError,
25
26
  SESSION_COOKIE,
26
27
  SESSION_MAX_AGE_SECONDS,
28
+ SessionExpiredError,
27
29
  deriveUiCapabilities,
28
30
  isSameOriginRequest,
29
31
  requireSessionSecret,
@@ -74,6 +74,12 @@ var LastOwnerError = class extends Error {
74
74
  this.name = "LastOwnerError";
75
75
  }
76
76
  };
77
+ var SessionExpiredError = class extends Error {
78
+ constructor(message = "Your session has expired. Please request a new link.") {
79
+ super(message);
80
+ this.name = "SessionExpiredError";
81
+ }
82
+ };
77
83
 
78
84
  export {
79
85
  deriveUiCapabilities,
@@ -86,5 +92,6 @@ export {
86
92
  signAudienceToken,
87
93
  verifyAudienceToken,
88
94
  setSessionCookie,
89
- LastOwnerError
95
+ LastOwnerError,
96
+ SessionExpiredError
90
97
  };
@@ -9,4 +9,30 @@ export type ViewBranch = "saved" | "live";
9
9
  * `archived` — so edits to live content show alongside drafts.
10
10
  */
11
11
  export declare function isSectionVisibleInViewBranch(status: string, viewBranch: ViewBranch): boolean;
12
+ /**
13
+ * Which sections to render in the editor's view mode.
14
+ *
15
+ * - Edit mode: the in-memory sections as-is (edit chrome handles them).
16
+ * - "Unpublished" (saved): the editor's CURRENT in-memory content — all edits
17
+ * minus archived. It must NOT depend on a re-fetched branch: after a publish
18
+ * the `saved` branch is deleted and `/api/content?branch=saved` falls back to
19
+ * `main`, which would clobber unsaved edits (they'd flash then vanish).
20
+ * - "Published" (live): the deployed site — the fetched `main` branch filtered to
21
+ * `status === "live"`, in page order. Renders nothing until the fetch resolves
22
+ * so in-memory edits never flash as if they were published.
23
+ */
24
+ export declare function computeDisplaySections<T extends {
25
+ section: {
26
+ id: string;
27
+ };
28
+ meta: {
29
+ status: string;
30
+ };
31
+ }>(args: {
32
+ isEditMode: boolean;
33
+ viewBranch: ViewBranch;
34
+ sections: T[];
35
+ viewSections: T[] | null;
36
+ activePageSectionIds: string[];
37
+ }): T[];
12
38
  //# sourceMappingURL=view-branch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"view-branch.d.ts","sourceRoot":"","sources":["../../../src/components/shell/view-branch.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1C;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAE5F"}
1
+ {"version":3,"file":"view-branch.d.ts","sourceRoot":"","sources":["../../../src/components/shell/view-branch.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1C;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAE5F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS;IAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,EAAE,IAAI,EAAE;IAC5G,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,CAAC,EAAE,CAAC;IACd,YAAY,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC;IACzB,oBAAoB,EAAE,MAAM,EAAE,CAAC;CAChC,GAAG,CAAC,EAAE,CAaN"}
package/dist/index.js CHANGED
@@ -20,13 +20,14 @@ import {
20
20
  LastOwnerError,
21
21
  SESSION_COOKIE,
22
22
  SESSION_MAX_AGE_SECONDS,
23
+ SessionExpiredError,
23
24
  deriveUiCapabilities,
24
25
  setSessionCookie,
25
26
  signAudienceToken,
26
27
  signSessionToken,
27
28
  verifyAudienceToken,
28
29
  verifySessionToken
29
- } from "./chunk-UNWNT52N.js";
30
+ } from "./chunk-EM5DFHDZ.js";
30
31
  import {
31
32
  AudienceColorSchema,
32
33
  AudienceNameSchema,
@@ -157,6 +158,7 @@ export {
157
158
  SESSION_COOKIE,
158
159
  SESSION_MAX_AGE_SECONDS,
159
160
  SectionMetaSchema,
161
+ SessionExpiredError,
160
162
  SessionSchema,
161
163
  SingleMediaReferenceSchema,
162
164
  SiteConfigSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawnagency/primitives",
3
- "version": "0.1.63",
3
+ "version": "0.1.65",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./package.json": "./package.json",
@@ -8,3 +8,19 @@ export class LastOwnerError extends Error {
8
8
  this.name = "LastOwnerError";
9
9
  }
10
10
  }
11
+
12
+ /**
13
+ * Thrown when an authenticated operation cannot proceed because the underlying
14
+ * Supabase session is missing, expired, or its refresh token is stale/used.
15
+ * This is distinct from a never-authenticated request: the caller passed the
16
+ * middleware gate (e.g. via a still-valid app session cookie) but the Supabase
17
+ * session needed for the operation is dead. API routes should translate this to
18
+ * HTTP 401 so the UI prompts the user to request a fresh link, rather than
19
+ * surfacing an opaque 500.
20
+ */
21
+ export class SessionExpiredError extends Error {
22
+ constructor(message = "Your session has expired. Please request a new link.") {
23
+ super(message);
24
+ this.name = "SessionExpiredError";
25
+ }
26
+ }
package/src/auth/index.ts CHANGED
@@ -17,4 +17,4 @@ export { deriveUiCapabilities } from "./capabilities";
17
17
 
18
18
  export { requireSessionSecret, isSameOriginRequest, safeNextPath } from "./security";
19
19
  export { signSessionToken, verifySessionToken, signAudienceToken, verifyAudienceToken, setSessionCookie, SESSION_COOKIE, AUDIENCE_COOKIE, SESSION_MAX_AGE_SECONDS, AUDIENCE_MAX_AGE_SECONDS } from "./cookies";
20
- export { LastOwnerError } from "./errors";
20
+ export { LastOwnerError, SessionExpiredError } from "./errors";
@@ -69,7 +69,7 @@ import { Button } from "../shared/Button";
69
69
  import { SplitButton } from "../shared/SplitButton";
70
70
  import { IconButton } from "../shared/IconButton";
71
71
  import { SegmentedControl } from "../shared/SegmentedControl";
72
- import { isSectionVisibleInViewBranch } from "./view-branch";
72
+ import { computeDisplaySections } from "./view-branch";
73
73
  import { SettingsIcon } from "../shared/icons";
74
74
  import { ImageIcon, ListOrderedIcon, FilesIcon, X } from "lucide-react";
75
75
  import { ErrorBoundary } from "../shared/ErrorBoundary";
@@ -1053,14 +1053,16 @@ function ViewBranchWatcher({
1053
1053
  const { isEditMode, viewBranch } = useEditorContext();
1054
1054
 
1055
1055
  useEffect(() => {
1056
- if (isEditMode) {
1056
+ // Only "Published" (live) needs a fetch — it previews the deployed main
1057
+ // branch. "Unpublished" (saved) renders the editor's in-memory edits, so no
1058
+ // fetch (fetching `saved` would 404 after a publish and fall back to main,
1059
+ // clobbering unsaved edits). Null viewSections routes to the in-memory path.
1060
+ if (isEditMode || viewBranch !== "live") {
1057
1061
  setViewSections(null);
1058
1062
  return;
1059
1063
  }
1060
1064
 
1061
- const branch = viewBranch === "live" ? "main" : "saved";
1062
-
1063
- fetch(`/api/content?branch=${branch}`)
1065
+ fetch(`/api/content?branch=main`)
1064
1066
  .then((res) => res.json())
1065
1067
  .then((data) => {
1066
1068
  setViewSections(data.sections);
@@ -1216,15 +1218,16 @@ function EditorContent({
1216
1218
 
1217
1219
  const editingEnabled = isEditMode && !isPublishing;
1218
1220
 
1219
- // View mode: render the active page's sections from the fetched branch, in page
1220
- // order. "Published" (live) shows only status === "live"; "Unpublished" (saved)
1221
- // shows all in-progress edits except archived (see view-branch.ts). Edit mode:
1222
- // the active page's in-memory sections (the `sections` prop is already `activeSections`).
1223
- const displaySections = !isEditMode && viewSections !== null
1224
- ? activePageSectionIds
1225
- .map((id) => viewSections.find((s) => s.section.id === id))
1226
- .filter((s): s is LoadedSection => !!s && isSectionVisibleInViewBranch(s.meta.status, viewBranch))
1227
- : sections;
1221
+ // "Published" (live) previews the deployed main branch (status === "live");
1222
+ // "Unpublished" (saved) previews the editor's current in-memory edits (all
1223
+ // statuses except archived) — see computeDisplaySections in view-branch.ts.
1224
+ const displaySections = computeDisplaySections({
1225
+ isEditMode,
1226
+ viewBranch,
1227
+ sections,
1228
+ viewSections,
1229
+ activePageSectionIds,
1230
+ });
1228
1231
 
1229
1232
  useEffect(() => {
1230
1233
  return combine(
@@ -12,3 +12,36 @@ export type ViewBranch = "saved" | "live";
12
12
  export function isSectionVisibleInViewBranch(status: string, viewBranch: ViewBranch): boolean {
13
13
  return viewBranch === "live" ? status === "live" : status !== "archived";
14
14
  }
15
+
16
+ /**
17
+ * Which sections to render in the editor's view mode.
18
+ *
19
+ * - Edit mode: the in-memory sections as-is (edit chrome handles them).
20
+ * - "Unpublished" (saved): the editor's CURRENT in-memory content — all edits
21
+ * minus archived. It must NOT depend on a re-fetched branch: after a publish
22
+ * the `saved` branch is deleted and `/api/content?branch=saved` falls back to
23
+ * `main`, which would clobber unsaved edits (they'd flash then vanish).
24
+ * - "Published" (live): the deployed site — the fetched `main` branch filtered to
25
+ * `status === "live"`, in page order. Renders nothing until the fetch resolves
26
+ * so in-memory edits never flash as if they were published.
27
+ */
28
+ export function computeDisplaySections<T extends { section: { id: string }; meta: { status: string } }>(args: {
29
+ isEditMode: boolean;
30
+ viewBranch: ViewBranch;
31
+ sections: T[];
32
+ viewSections: T[] | null;
33
+ activePageSectionIds: string[];
34
+ }): T[] {
35
+ const { isEditMode, viewBranch, sections, viewSections, activePageSectionIds } = args;
36
+
37
+ if (isEditMode) return sections;
38
+
39
+ if (viewBranch === "saved") {
40
+ return sections.filter((s) => isSectionVisibleInViewBranch(s.meta.status, "saved"));
41
+ }
42
+
43
+ if (viewSections === null) return [];
44
+ return activePageSectionIds
45
+ .map((id) => viewSections.find((s) => s.section.id === id))
46
+ .filter((s): s is T => !!s && isSectionVisibleInViewBranch(s.meta.status, "live"));
47
+ }