@drawnagency/primitives 0.1.117 → 0.1.118

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.
@@ -66,6 +66,22 @@ function useEditablePlainText({
66
66
  }
67
67
 
68
68
  // src/components/primitives/EditablePlainText.tsx
69
+ function caretOffset(el) {
70
+ const sel = window.getSelection();
71
+ if (!sel || sel.rangeCount === 0) return null;
72
+ const range = sel.getRangeAt(0);
73
+ if (!el.contains(range.endContainer)) return null;
74
+ const before = document.createRange();
75
+ before.selectNodeContents(el);
76
+ before.setEnd(range.endContainer, range.endOffset);
77
+ return before.toString().length;
78
+ }
79
+ function placeCaret(el, offset) {
80
+ const sel = window.getSelection();
81
+ if (!sel) return;
82
+ if (el.firstChild) sel.collapse(el.firstChild, offset);
83
+ else sel.collapse(el, 0);
84
+ }
69
85
  function EditablePlainText({
70
86
  tag,
71
87
  value,
@@ -86,9 +102,11 @@ function EditablePlainText({
86
102
  shouldPreventEnter
87
103
  } = useEditablePlainText({ value, onChange, multiline });
88
104
  useEffect2(() => {
89
- if (ref.current && ref.current.textContent !== localValue) {
90
- ref.current.textContent = localValue;
91
- }
105
+ const el = ref.current;
106
+ if (!el || el.textContent === localValue) return;
107
+ const caret = document.activeElement === el ? caretOffset(el) : null;
108
+ el.textContent = localValue;
109
+ if (caret !== null) placeCaret(el, Math.min(caret, localValue.length));
92
110
  }, [localValue]);
93
111
  const onInput = useCallback2(() => {
94
112
  if (ref.current) {
@@ -4,7 +4,7 @@ import {
4
4
  pluralize,
5
5
  stripHtmlToPlainText,
6
6
  truncate
7
- } from "./chunk-LNQUUC2S.js";
7
+ } from "./chunk-HRX3XJBU.js";
8
8
  import {
9
9
  DeleteButton,
10
10
  DragHandle,
@@ -25,7 +25,7 @@ import {
25
25
  EditableRichText,
26
26
  defineHeadingSection,
27
27
  useSanitizedHtml
28
- } from "./chunk-HUOW3ZV2.js";
28
+ } from "./chunk-ZOTRHHUB.js";
29
29
  import {
30
30
  EditablePlainText,
31
31
  ImageDropZone,
@@ -35,7 +35,7 @@ import {
35
35
  containerGridClass,
36
36
  masonryColumnsClass,
37
37
  spacerHiddenClass
38
- } from "./chunk-L7WTERU5.js";
38
+ } from "./chunk-4NSOKEA7.js";
39
39
  import {
40
40
  DropEdgeIndicator,
41
41
  useMediaLibrary,
@@ -9,7 +9,7 @@ import {
9
9
  MediaBlock,
10
10
  containerGridClass,
11
11
  useEditableCollection
12
- } from "./chunk-L7WTERU5.js";
12
+ } from "./chunk-4NSOKEA7.js";
13
13
  import {
14
14
  DragHandle,
15
15
  DropEdgeIndicator,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  EditablePlainText
3
- } from "./chunk-L7WTERU5.js";
3
+ } from "./chunk-4NSOKEA7.js";
4
4
  import {
5
5
  usePages
6
6
  } from "./chunk-RIITGTV3.js";
@@ -1 +1 @@
1
- {"version":3,"file":"EditablePlainText.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/EditablePlainText.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,OAAO,CAAC;AAGf,UAAU,sBACR,SAAQ,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,UAAU,CAAC;IACrD,GAAG,EAAE,WAAW,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,GAAG,EACH,KAAK,EACL,QAAQ,EACR,UAAU,EACV,SAAiB,EACjB,WAAW,EACX,SAAS,EACT,GAAG,SAAS,EACb,EAAE,sBAAsB,0FA+ExB"}
1
+ {"version":3,"file":"EditablePlainText.d.ts","sourceRoot":"","sources":["../../../src/components/primitives/EditablePlainText.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,OAAO,CAAC;AAGf,UAAU,sBACR,SAAQ,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,UAAU,CAAC;IACrD,GAAG,EAAE,WAAW,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAqBD,wBAAgB,iBAAiB,CAAC,EAChC,GAAG,EACH,KAAK,EACL,QAAQ,EACR,UAAU,EACV,SAAiB,EACjB,WAAW,EACX,SAAS,EACT,GAAG,SAAS,EACb,EAAE,sBAAsB,0FAoFxB"}
@@ -2,7 +2,7 @@ import {
2
2
  EditableRichText,
3
3
  LinkPopover,
4
4
  defineHeadingSection
5
- } from "../../chunk-HUOW3ZV2.js";
5
+ } from "../../chunk-ZOTRHHUB.js";
6
6
  import {
7
7
  EditableGrid,
8
8
  EditableList,
@@ -10,7 +10,7 @@ import {
10
10
  ImageDropZone,
11
11
  MediaBlock,
12
12
  ResolvedMedia
13
- } from "../../chunk-L7WTERU5.js";
13
+ } from "../../chunk-4NSOKEA7.js";
14
14
  import {
15
15
  LinkField,
16
16
  PagesContext,
@@ -2,9 +2,9 @@ import {
2
2
  ColorItemSchema,
3
3
  ColorSpaceSchema,
4
4
  brandGuideSectionDefs
5
- } from "../../chunk-LNQUUC2S.js";
5
+ } from "../../chunk-HRX3XJBU.js";
6
6
  import "../../chunk-JR24U7FJ.js";
7
- import "../../chunk-L7WTERU5.js";
7
+ import "../../chunk-4NSOKEA7.js";
8
8
  import "../../chunk-RIITGTV3.js";
9
9
  import "../../chunk-DIMDRBFK.js";
10
10
  import "../../chunk-IIDKW7AV.js";
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  allSectionDefs
3
- } from "../../chunk-MSBHMY3C.js";
4
- import "../../chunk-LNQUUC2S.js";
3
+ } from "../../chunk-ERLIL4RU.js";
4
+ import "../../chunk-HRX3XJBU.js";
5
5
  import "../../chunk-CEFFP3CH.js";
6
6
  import "../../chunk-JR24U7FJ.js";
7
- import "../../chunk-HUOW3ZV2.js";
8
- import "../../chunk-L7WTERU5.js";
7
+ import "../../chunk-ZOTRHHUB.js";
8
+ import "../../chunk-4NSOKEA7.js";
9
9
  import "../../chunk-RIITGTV3.js";
10
10
  import "../../chunk-JCDZAFX2.js";
11
11
  import "../../chunk-DIMDRBFK.js";
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  allSectionDefs
3
- } from "../../chunk-MSBHMY3C.js";
4
- import "../../chunk-LNQUUC2S.js";
3
+ } from "../../chunk-ERLIL4RU.js";
4
+ import "../../chunk-HRX3XJBU.js";
5
5
  import "../../chunk-CEFFP3CH.js";
6
6
  import "../../chunk-JR24U7FJ.js";
7
- import "../../chunk-HUOW3ZV2.js";
8
- import "../../chunk-L7WTERU5.js";
7
+ import "../../chunk-ZOTRHHUB.js";
8
+ import "../../chunk-4NSOKEA7.js";
9
9
  import "../../chunk-RIITGTV3.js";
10
10
  import "../../chunk-JCDZAFX2.js";
11
11
  import "../../chunk-DIMDRBFK.js";
@@ -23,12 +23,14 @@ interface Props {
23
23
  * an audience-filtered site.
24
24
  */
25
25
  canImpersonate?: boolean;
26
+ /** Open the view in a new tab — set by the editor, so it keeps the editor tab. */
27
+ viewAsInNewTab?: boolean;
26
28
  }
27
29
  /**
28
30
  * The Audience Details page body: who can see what, and what password to share.
29
31
  * Reachable by editors and the site's default audience (the client), so the
30
32
  * client can hand a new partner access without going through us.
31
33
  */
32
- export declare function AudienceDetailsPanel({ audiences, revealSupported, canImpersonate }: Props): import("react/jsx-runtime").JSX.Element;
34
+ export declare function AudienceDetailsPanel({ audiences, revealSupported, canImpersonate, viewAsInNewTab }: Props): import("react/jsx-runtime").JSX.Element;
33
35
  export default AudienceDetailsPanel;
34
36
  //# sourceMappingURL=AudienceDetailsPanel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AudienceDetailsPanel.d.ts","sourceRoot":"","sources":["../../../src/components/shell/AudienceDetailsPanel.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAgB,MAAM,6BAA6B,CAAC;AAElF,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,8EAA8E;IAC9E,WAAW,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAC/D,QAAQ,EAAE,gBAAgB,CAAC;CAC5B;AAED,UAAU,KAAK;IACb,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,8EAA8E;IAC9E,eAAe,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAoOD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,cAAsB,EAAE,EAAE,KAAK,2CAgBjG;AAED,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"AudienceDetailsPanel.d.ts","sourceRoot":"","sources":["../../../src/components/shell/AudienceDetailsPanel.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAgB,MAAM,6BAA6B,CAAC;AAElF,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,8EAA8E;IAC9E,WAAW,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAC/D,QAAQ,EAAE,gBAAgB,CAAC;CAC5B;AAED,UAAU,KAAK;IACb,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,8EAA8E;IAC9E,eAAe,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kFAAkF;IAClF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAoOD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,cAAsB,EAAE,cAAsB,EAAE,EAAE,KAAK,2CAgBzH;AAED,eAAe,oBAAoB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAejD,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAuDtF,OAAO,KAAK,EAAE,eAAe,EAA2D,MAAM,oBAAoB,CAAC;AA6DnH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AA8BxD,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAIhB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,YAAY,EAAE;QACZ,KAAK,EAAE,OAAO,CAAC;QACf,aAAa,EAAE,OAAO,CAAC;QACvB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,OAAO,CAAC;QACxB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAC;IAIhE,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAKjC,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CAC1C;AAQD,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,YAAY,EACZ,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,EACX,sBAA8B,EAC9B,eAAe,GAChB,EAAE,KAAK,2CAsvEP;AA8ED;6EAC6E;AAC7E,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,WAAW,CAAC,YAAY,CAAC,EACvC,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,eAAe,EAAE,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAK/B"}
1
+ {"version":3,"file":"EditorShell.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorShell.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAejD,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAuDtF,OAAO,KAAK,EAAE,eAAe,EAA2D,MAAM,oBAAoB,CAAC;AA6DnH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AA8BxD,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAIhB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,YAAY,EAAE;QACZ,KAAK,EAAE,OAAO,CAAC;QACf,aAAa,EAAE,OAAO,CAAC;QACvB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,OAAO,CAAC;QACxB,kBAAkB,EAAE,OAAO,CAAC;QAC5B,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAA;KAAE,GAAG,IAAI,CAAC;IAIhE,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAKjC,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;CAC1C;AAQD,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAClC,OAAO,EACP,YAAY,EACZ,MAAM,EACN,SAAS,EAAE,gBAAgB,EAC3B,YAAY,EACZ,WAAW,EACX,sBAA8B,EAC9B,eAAe,GAChB,EAAE,KAAK,2CAkwEP;AA8ED;6EAC6E;AAC7E,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,WAAW,CAAC,YAAY,CAAC,EACvC,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,eAAe,EAAE,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAK/B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawnagency/primitives",
3
- "version": "0.1.117",
3
+ "version": "0.1.118",
4
4
  "description": "Browser-safe foundation of the Brand Portal framework: Zod schemas, section components, editor shell, hooks, pure libs, collab contract, document engine.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -18,6 +18,25 @@ interface EditablePlainTextProps
18
18
  placeholder?: string;
19
19
  }
20
20
 
21
+ function caretOffset(el: HTMLElement): number | null {
22
+ const sel = window.getSelection();
23
+ if (!sel || sel.rangeCount === 0) return null;
24
+ const range = sel.getRangeAt(0);
25
+ if (!el.contains(range.endContainer)) return null;
26
+ const before = document.createRange();
27
+ before.selectNodeContents(el);
28
+ before.setEnd(range.endContainer, range.endOffset);
29
+ return before.toString().length;
30
+ }
31
+
32
+ function placeCaret(el: HTMLElement, offset: number) {
33
+ const sel = window.getSelection();
34
+ if (!sel) return;
35
+ // textContent leaves a single text node, or none for an empty string.
36
+ if (el.firstChild) sel.collapse(el.firstChild, offset);
37
+ else sel.collapse(el, 0);
38
+ }
39
+
21
40
  export function EditablePlainText({
22
41
  tag,
23
42
  value,
@@ -48,9 +67,14 @@ export function EditablePlainText({
48
67
 
49
68
  // Set textContent via ref (safe — plain text only, no HTML injection)
50
69
  useEffect(() => {
51
- if (ref.current && ref.current.textContent !== localValue) {
52
- ref.current.textContent = localValue;
53
- }
70
+ const el = ref.current;
71
+ if (!el || el.textContent === localValue) return;
72
+ // A value that differs from the DOM while the field is focused (a peer
73
+ // save, a rebase or a 409 recovery landing mid-typing) replaces the text
74
+ // node, which collapses the caret to offset 0. Put it back, clamped.
75
+ const caret = document.activeElement === el ? caretOffset(el) : null;
76
+ el.textContent = localValue;
77
+ if (caret !== null) placeCaret(el, Math.min(caret, localValue.length));
54
78
  }, [localValue]);
55
79
 
56
80
  const onInput = useCallback(() => {
@@ -27,6 +27,8 @@ interface Props {
27
27
  * an audience-filtered site.
28
28
  */
29
29
  canImpersonate?: boolean;
30
+ /** Open the view in a new tab — set by the editor, so it keeps the editor tab. */
31
+ viewAsInNewTab?: boolean;
30
32
  }
31
33
 
32
34
  const PLACEHOLDER_COLOR = "#94a3b8";
@@ -174,7 +176,7 @@ function PageRow({ page }: { page: OverviewPage }) {
174
176
  );
175
177
  }
176
178
 
177
- function AudienceBlock({ audience, revealSupported, canImpersonate }: { audience: AudienceDetail; revealSupported: boolean; canImpersonate: boolean }) {
179
+ function AudienceBlock({ audience, revealSupported, canImpersonate, viewAsInNewTab }: { audience: AudienceDetail; revealSupported: boolean; canImpersonate: boolean; viewAsInNewTab: boolean }) {
178
180
  const [open, setOpen] = useState(false);
179
181
  const { overview } = audience;
180
182
 
@@ -208,7 +210,7 @@ function AudienceBlock({ audience, revealSupported, canImpersonate }: { audience
208
210
  </button>
209
211
  {canImpersonate && (
210
212
  // A plain form POST: the route sets the cookie and 303s to the viewer site.
211
- <form method="post" action="/api/auth/impersonate" className="shrink-0">
213
+ <form method="post" action="/api/auth/impersonate" target={viewAsInNewTab ? "_blank" : undefined} className="shrink-0">
212
214
  <input type="hidden" name="audience" value={audience.name} />
213
215
  <Button type="submit" variant="secondary" size="sm" aria-label={`View the site as ${audience.displayName}`}>
214
216
  <span className="inline-flex items-center gap-1.5">
@@ -260,7 +262,7 @@ function AudienceBlock({ audience, revealSupported, canImpersonate }: { audience
260
262
  * Reachable by editors and the site's default audience (the client), so the
261
263
  * client can hand a new partner access without going through us.
262
264
  */
263
- export function AudienceDetailsPanel({ audiences, revealSupported, canImpersonate = false }: Props) {
265
+ export function AudienceDetailsPanel({ audiences, revealSupported, canImpersonate = false, viewAsInNewTab = false }: Props) {
264
266
  if (audiences.length === 0) {
265
267
  return (
266
268
  <p className="text-sm text-base-contrast-light">
@@ -272,7 +274,7 @@ export function AudienceDetailsPanel({ audiences, revealSupported, canImpersonat
272
274
  return (
273
275
  <div className="flex flex-col gap-3">
274
276
  {audiences.map((a) => (
275
- <AudienceBlock key={a.name} audience={a} revealSupported={revealSupported} canImpersonate={canImpersonate} />
277
+ <AudienceBlock key={a.name} audience={a} revealSupported={revealSupported} canImpersonate={canImpersonate} viewAsInNewTab={viewAsInNewTab} />
276
278
  ))}
277
279
  </div>
278
280
  );
@@ -328,6 +328,9 @@ export default function EditorShell({
328
328
  // null = the auth provider has no named sign-ins (`.env` mode → route 501s),
329
329
  // which is also what turns the reveal buttons off.
330
330
  const [viewerCredentials, setViewerCredentials] = useState<ViewerCredential[] | null>(null);
331
+ // Gates "View as" in Audience Details: without password protection no visitor
332
+ // sees an audience-filtered site, and the impersonate route refuses.
333
+ const [passwordEnabled, setPasswordEnabled] = useState(false);
331
334
  const [movingSectionId, setMovingSectionId] = useState<string | null>(null);
332
335
  // Set by recoverConflict when a rebase surfaces overlapping edits; drives the
333
336
  // keep-mine/take-theirs dialog. `resolve` is the promise resolver recovery
@@ -1221,6 +1224,15 @@ export default function EditorShell({
1221
1224
  if (!cancelled) setViewerCredentials(null);
1222
1225
  }
1223
1226
  })();
1227
+ (async () => {
1228
+ try {
1229
+ const res = await fetch("/api/auth/audiences");
1230
+ const enabled = res.ok && (await res.json()).passwordEnabled === true;
1231
+ if (!cancelled) setPasswordEnabled(enabled);
1232
+ } catch {
1233
+ if (!cancelled) setPasswordEnabled(false);
1234
+ }
1235
+ })();
1224
1236
  return () => { cancelled = true; };
1225
1237
  }, [showAudienceDetails]);
1226
1238
 
@@ -2308,7 +2320,7 @@ export default function EditorShell({
2308
2320
  current working copy — add or remove sign-ins under Site settings → Audiences.
2309
2321
  </p>
2310
2322
  <div className="mt-6">
2311
- <AudienceDetailsPanel audiences={audienceDetails} revealSupported={viewerCredentials !== null} />
2323
+ <AudienceDetailsPanel audiences={audienceDetails} revealSupported={viewerCredentials !== null} canImpersonate={passwordEnabled} viewAsInNewTab />
2312
2324
  </div>
2313
2325
  </div>
2314
2326
  ) : (