@coffer-org/plugin-webchat 5.0.0 → 6.0.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.
@@ -23959,42 +23959,35 @@ function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
23959
23959
  if (ladder) {
23960
23960
  const level = ladder.indexOf(current) + 1;
23961
23961
  const shown = preview ?? level;
23962
- const previewRow = preview === null ? void 0 : ladder[preview - 1];
23963
23962
  const levelName = (row, n) => `${row.label} · ${t("webchat.level", {
23964
23963
  value: n,
23965
23964
  max: ladder.length
23966
23965
  })}`;
23967
- return /* @__PURE__ */ jsxs("div", {
23968
- className: "flex min-w-0 items-center gap-1.5",
23969
- children: [/* @__PURE__ */ jsx("ul", {
23970
- "aria-label": t("webchat.agentPicker"),
23971
- className: "flex shrink-0 items-center",
23972
- onPointerLeave: () => setPreview(null),
23973
- children: ladder.map((r, i) => {
23974
- const n = i + 1;
23975
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
23976
- type: "button",
23977
- ...r === current ? { "aria-current": true } : {},
23978
- "aria-label": levelName(r, n),
23979
- className: "flex rounded p-1 focus-visible:outline-2 focus-visible:outline-accent",
23980
- onPointerEnter: () => setPreview(n),
23981
- onFocus: () => setPreview(n),
23982
- onBlur: () => setPreview(null),
23983
- onClick: () => onChange({
23984
- agentId: r.agentId,
23985
- presetId: r.presetId
23986
- }),
23987
- children: /* @__PURE__ */ jsx(Star, {
23988
- size: 16,
23989
- "aria-hidden": true,
23990
- className: n <= shown ? "fill-yellow-400 text-yellow-400" : "text-border"
23991
- })
23992
- }) }, `${r.agentId}/${r.presetId}`);
23993
- })
23994
- }), previewRow && /* @__PURE__ */ jsx("span", {
23995
- className: "min-w-0 truncate text-xs text-muted",
23996
- children: previewRow.hint || previewRow.label
23997
- })]
23966
+ return /* @__PURE__ */ jsx("ul", {
23967
+ "aria-label": t("webchat.agentPicker"),
23968
+ className: "flex shrink-0 items-center",
23969
+ onPointerLeave: () => setPreview(null),
23970
+ children: ladder.map((r, i) => {
23971
+ const n = i + 1;
23972
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
23973
+ type: "button",
23974
+ ...r === current ? { "aria-current": true } : {},
23975
+ "aria-label": levelName(r, n),
23976
+ className: "flex rounded p-1 focus-visible:outline-2 focus-visible:outline-accent",
23977
+ onPointerEnter: () => setPreview(n),
23978
+ onFocus: () => setPreview(n),
23979
+ onBlur: () => setPreview(null),
23980
+ onClick: () => onChange({
23981
+ agentId: r.agentId,
23982
+ presetId: r.presetId
23983
+ }),
23984
+ children: /* @__PURE__ */ jsx(Star, {
23985
+ size: 16,
23986
+ "aria-hidden": true,
23987
+ className: n <= shown ? "fill-yellow-400 text-yellow-400" : "text-border"
23988
+ })
23989
+ }) }, `${r.agentId}/${r.presetId}`);
23990
+ })
23998
23991
  });
23999
23992
  }
24000
23993
  return /* @__PURE__ */ jsxs("div", {
@@ -24294,12 +24287,31 @@ function ChatDropSurface({ children, onFiles, className }) {
24294
24287
  })]
24295
24288
  });
24296
24289
  }
24297
- /** What to do with the widget after following a link from a response. On desktop a 380px
24298
- * panel does not obstruct the page (a darkened modal does, so it collapses into it);
24299
- * on narrow screens the chat remains expanded so the user does not lose the active
24300
- * conversation after an in-app navigation. */
24301
- function nextModeAfterNav(wideNav) {
24302
- return wideNav ? "panel" : "modal";
24290
+ /** The states the VIEWPORT offers, ordered from least to most expanded. `panel` the 380px
24291
+ * floating window — exists only where it does not obstruct the page: on a narrow screen the
24292
+ * widget has exactly two states, the bubble and the full sheet. Every mode transition is a
24293
+ * move along this ladder, so no call site can put the widget into a mode its screen lacks. */
24294
+ function chatModes(wide) {
24295
+ return wide ? [
24296
+ "bubble",
24297
+ "panel",
24298
+ "modal"
24299
+ ] : ["bubble", "modal"];
24300
+ }
24301
+ /** Where the widget rests once it is opened — expanded, but as little as the viewport allows.
24302
+ * Also where it returns after following a link from a response: on a wide screen the panel
24303
+ * does not obstruct the page the user just navigated to, while on a narrow one the sheet is
24304
+ * the only visible state, so the active conversation survives the navigation either way. */
24305
+ function restingMode(wide) {
24306
+ return chatModes(wide)[1];
24307
+ }
24308
+ /** One step along the ladder, clamped at both ends: `-1` is what collapse, Esc and a dialog
24309
+ * dismiss do, `+1` is expand. A mode this viewport no longer offers is re-homed by the effect
24310
+ * in the widget, not by stepping — the step always returns a mode from the current ladder. */
24311
+ function stepMode(mode, wide, delta) {
24312
+ const ladder = chatModes(wide);
24313
+ const next = ladder.indexOf(mode) + delta;
24314
+ return ladder[Math.min(ladder.length - 1, Math.max(0, next))];
24303
24315
  }
24304
24316
  function ChatWidget() {
24305
24317
  const { t } = useTranslation();
@@ -24313,20 +24325,22 @@ function ChatWidget() {
24313
24325
  const [uploadError, setUploadError] = useState(null);
24314
24326
  const [retryFiles, setRetryFiles] = useState([]);
24315
24327
  const wide = useMinWidth("sm", 640);
24316
- const wideNav = useMinWidth("nav", 768);
24317
24328
  const onChatNavigate = useCallback(() => {
24318
- setMode(nextModeAfterNav(wideNav));
24319
- }, [wideNav]);
24329
+ setMode(restingMode(wide));
24330
+ }, [wide]);
24320
24331
  const expandBtnRef = useRef(null);
24321
24332
  const prevModeRef = useRef("bubble");
24322
24333
  useEffect(() => {
24323
24334
  if (mode !== "modal") return;
24324
24335
  const onKey = (e) => {
24325
- if (e.key === "Escape") setMode("panel");
24336
+ if (e.key === "Escape") setMode(stepMode("modal", wide, -1));
24326
24337
  };
24327
24338
  window.addEventListener("keydown", onKey);
24328
24339
  return () => window.removeEventListener("keydown", onKey);
24329
- }, [mode]);
24340
+ }, [mode, wide]);
24341
+ useEffect(() => {
24342
+ if (!chatModes(wide).includes(mode)) setMode(restingMode(wide));
24343
+ }, [mode, wide]);
24330
24344
  useEffect(() => {
24331
24345
  if (mode === "panel" && prevModeRef.current === "modal") expandBtnRef.current?.focus();
24332
24346
  if (mode !== "modal") setThreadsOpen(false);
@@ -24429,11 +24443,11 @@ function ChatWidget() {
24429
24443
  onClick: startNew,
24430
24444
  children: /* @__PURE__ */ jsx(Plus, { size: 16 })
24431
24445
  }),
24432
- /* @__PURE__ */ jsx("button", {
24446
+ chatModes(wide).includes("panel") && /* @__PURE__ */ jsx("button", {
24433
24447
  ref: expandBtnRef,
24434
24448
  "aria-label": mode === "modal" ? t("webchat.collapse") : t("webchat.expand"),
24435
24449
  className: "rounded p-1 hover:bg-elevated",
24436
- onClick: () => setMode(mode === "modal" ? "panel" : "modal"),
24450
+ onClick: () => setMode(stepMode(mode, wide, mode === "modal" ? -1 : 1)),
24437
24451
  children: mode === "modal" ? /* @__PURE__ */ jsx(Minimize2, { size: 16 }) : /* @__PURE__ */ jsx(Maximize2, { size: 16 })
24438
24452
  }),
24439
24453
  /* @__PURE__ */ jsx("button", {
@@ -24489,7 +24503,7 @@ function ChatWidget() {
24489
24503
  });
24490
24504
  if (mode === "bubble") return /* @__PURE__ */ jsx("button", {
24491
24505
  "aria-label": t("webchat.open"),
24492
- onClick: () => setMode(wide ? "panel" : "modal"),
24506
+ onClick: () => setMode(restingMode(wide)),
24493
24507
  className: "fixed bottom-20 right-4 z-40 grid h-14 w-14 place-items-center rounded-full bg-accent text-white shadow-lg nav:bottom-4",
24494
24508
  children: /* @__PURE__ */ jsx(MessageCircle, { size: 24 })
24495
24509
  });
@@ -24501,7 +24515,7 @@ function ChatWidget() {
24501
24515
  return /* @__PURE__ */ jsxs(Dialog, {
24502
24516
  open: true,
24503
24517
  modal: false,
24504
- onOpenChange: (open) => !open && setMode("panel"),
24518
+ onOpenChange: (open) => !open && setMode(stepMode("modal", wide, -1)),
24505
24519
  children: [/* @__PURE__ */ jsx("div", {
24506
24520
  "data-chat-backdrop": true,
24507
24521
  "aria-hidden": "true",
@@ -24536,4 +24550,4 @@ function ChatWidget() {
24536
24550
  });
24537
24551
  }
24538
24552
  //#endregion
24539
- export { ChatWidget, ChatWidget as default, nextModeAfterNav };
24553
+ export { ChatWidget, ChatWidget as default, chatModes, restingMode, stepMode };
package/dist/schema.js CHANGED
@@ -4354,6 +4354,13 @@ function wrapKey(key, opts, meta) {
4354
4354
  editor: opts.editor
4355
4355
  }
4356
4356
  };
4357
+ if (opts.activate) m = {
4358
+ ...m,
4359
+ hints: {
4360
+ ...m.hints,
4361
+ activate: opts.activate
4362
+ }
4363
+ };
4357
4364
  if (opts.span !== void 0) m = {
4358
4365
  ...m,
4359
4366
  hints: {
@@ -4754,6 +4761,7 @@ function resolveUnits(u) {
4754
4761
  function normalizeOpts(rawIn) {
4755
4762
  const raw = rawIn;
4756
4763
  if (raw["view"] !== void 0) throw new Error("[normalizeOpts] `view` is gone — presentational options live in `ui: {}` (see CLAUDE.md § field author options and the field-types skill).");
4764
+ if (raw.ui?.["agent"] !== void 0) throw new Error("[normalizeOpts] `agent` is not presentation — declare it at the top level of the field call, not in `ui: {}`.");
4757
4765
  const r = raw.rules ?? {};
4758
4766
  const v = raw.ui ?? {};
4759
4767
  const config = {};
@@ -4790,7 +4798,8 @@ function normalizeOpts(rawIn) {
4790
4798
  emphasis: v.emphasis,
4791
4799
  noLabel: v.noLabel,
4792
4800
  role: v.role,
4793
- editor: v.editor
4801
+ editor: v.editor,
4802
+ activate: v.activate
4794
4803
  };
4795
4804
  return Object.fromEntries(Object.entries(result).filter(([, val]) => val !== void 0));
4796
4805
  }
@@ -9542,10 +9551,11 @@ var field = new Proxy({}, {
9542
9551
  has: (_t, k) => k in composedField()
9543
9552
  });
9544
9553
  function toClient(field) {
9545
- const { kind, label, required, prim, hints, options, strict, relation, json, hidden, derived, parts, cache } = field;
9554
+ const { kind, label, agent, required, prim, hints, options, strict, relation, json, hidden, derived, parts, cache } = field;
9546
9555
  return {
9547
9556
  kind,
9548
9557
  label,
9558
+ ...agent !== void 0 && { agent },
9549
9559
  required,
9550
9560
  prim,
9551
9561
  hints,
@@ -9952,6 +9962,7 @@ function materialize(decl, name) {
9952
9962
  const base = {
9953
9963
  kind: type.kind,
9954
9964
  label: opts.label ?? "",
9965
+ ...opts.agent !== void 0 && { agent: opts.agent },
9955
9966
  required,
9956
9967
  prim: type.prim,
9957
9968
  column: built.column,
@@ -9995,6 +10006,7 @@ function collectionGroup(key, opts, parts, collectionView) {
9995
10006
  multiple: true,
9996
10007
  required: opts.required ?? false,
9997
10008
  label: opts.label ?? key,
10009
+ ...opts.agent !== void 0 && { agent: opts.agent },
9998
10010
  display: "wrap",
9999
10011
  kind: collectionView,
10000
10012
  fields: rowFields
package/dist/web.js CHANGED
@@ -11,7 +11,7 @@ import { definePluginUI } from "@coffer-org/web-ui";
11
11
  var ui_default = definePluginUI({ slots: [{
12
12
  slot: "overlay",
13
13
  id: "webchat",
14
- load: () => import("./ChatWidget-uAyA3y0W.js")
14
+ load: () => import("./ChatWidget-DFVDexpD.js")
15
15
  }] });
16
16
  //#endregion
17
17
  export { ui_default as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffer-org/plugin-webchat",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"
@@ -27,8 +27,8 @@
27
27
  "test:ui": "vitest run --root ."
28
28
  },
29
29
  "dependencies": {
30
- "@coffer-org/sdk": "^5.0.0",
31
- "@coffer-org/server": "^5.0.0",
30
+ "@coffer-org/sdk": "^6.0.0",
31
+ "@coffer-org/server": "^6.0.0",
32
32
  "lucide-react": "^1.17.0",
33
33
  "react-markdown": "^10.1.0",
34
34
  "remark-gfm": "^4.0.1"