@coffer-org/plugin-webchat 4.0.0 → 5.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.
- package/dist/{ChatWidget-Bs8zrQVZ.js → ChatWidget-uAyA3y0W.js} +43 -112
- package/dist/index.js +1 -1
- package/dist/runtime/format.d.ts +1 -0
- package/dist/runtime/format.js +5 -0
- package/dist/runtime/send.js +4 -4
- package/dist/schema.js +123 -128
- package/dist/web.js +1 -1
- package/package.json +3 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as __fedReact from "react";
|
|
2
|
-
import { Dialog, DialogContent,
|
|
2
|
+
import { Dialog, DialogContent, classifySiteHref, uploadFile, useMinWidth } from "@coffer-org/web-ui";
|
|
3
3
|
import { createContext, createElement, forwardRef, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { useLocation, useNavigate } from "react-router-dom";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -23722,54 +23722,6 @@ function remarkGfm(options) {
|
|
|
23722
23722
|
toMarkdownExtensions.push(gfmToMarkdown(settings));
|
|
23723
23723
|
}
|
|
23724
23724
|
//#endregion
|
|
23725
|
-
//#region src/ui/internal-link.ts
|
|
23726
|
-
var SEGMENT = /^[A-Za-z0-9._~%-]+$/;
|
|
23727
|
-
/** Returns captured ':param' values on a match, or null. '*' is legal only as the
|
|
23728
|
-
* last pattern segment, so this branch may return immediately without reading got to the end. */
|
|
23729
|
-
function matchesPattern(pathname, pattern) {
|
|
23730
|
-
const got = pathname.split("/").filter(Boolean);
|
|
23731
|
-
const want = pattern.split("/").filter(Boolean);
|
|
23732
|
-
const captured = {};
|
|
23733
|
-
for (let i = 0; i < want.length; i++) {
|
|
23734
|
-
const w = want[i];
|
|
23735
|
-
if (w === "*") return got.length >= i ? captured : null;
|
|
23736
|
-
const g = got[i];
|
|
23737
|
-
if (g === void 0) return null;
|
|
23738
|
-
if (w.startsWith(":")) {
|
|
23739
|
-
if (!SEGMENT.test(g)) return null;
|
|
23740
|
-
captured[w.slice(1)] = g;
|
|
23741
|
-
} else if (w !== g) return null;
|
|
23742
|
-
}
|
|
23743
|
-
return got.length === want.length ? captured : null;
|
|
23744
|
-
}
|
|
23745
|
-
function classifyHref(href, origin, routes, pairs = []) {
|
|
23746
|
-
const raw = href.trim();
|
|
23747
|
-
if (!raw || raw.startsWith("//")) return { kind: "text" };
|
|
23748
|
-
if (!raw.startsWith("/") && !/^https?:\/\//i.test(raw)) return { kind: "text" };
|
|
23749
|
-
let url;
|
|
23750
|
-
try {
|
|
23751
|
-
url = new URL(raw, origin);
|
|
23752
|
-
} catch {
|
|
23753
|
-
return { kind: "text" };
|
|
23754
|
-
}
|
|
23755
|
-
if (url.origin !== origin) return {
|
|
23756
|
-
kind: "external",
|
|
23757
|
-
href: url.href
|
|
23758
|
-
};
|
|
23759
|
-
for (const r of routes) {
|
|
23760
|
-
const captured = matchesPattern(url.pathname, r.pattern);
|
|
23761
|
-
if (!captured) continue;
|
|
23762
|
-
if (pairs.length && captured["library"] !== void 0 && captured["shelf"] !== void 0) {
|
|
23763
|
-
if (!pairs.some((p) => p.library === captured["library"] && p.shelf === captured["shelf"])) return { kind: "text" };
|
|
23764
|
-
}
|
|
23765
|
-
return {
|
|
23766
|
-
kind: "internal",
|
|
23767
|
-
to: `${url.pathname}${url.search}${url.hash}`
|
|
23768
|
-
};
|
|
23769
|
-
}
|
|
23770
|
-
return { kind: "text" };
|
|
23771
|
-
}
|
|
23772
|
-
//#endregion
|
|
23773
23725
|
//#region src/ui/chat-nav.tsx
|
|
23774
23726
|
/** What to do with the widget when the user follows a link from a response.
|
|
23775
23727
|
* ChatWidget supplies the callback (it owns mode/open), avoiding a prop
|
|
@@ -23811,8 +23763,7 @@ function ChatTable(props) {
|
|
|
23811
23763
|
function ChatLink({ href, children }) {
|
|
23812
23764
|
const onNavigate = useChatNav();
|
|
23813
23765
|
const navigate = useNavigate();
|
|
23814
|
-
const
|
|
23815
|
-
const kind = classifyHref(href ?? "", window.location.origin, routes, getSiteShelves());
|
|
23766
|
+
const kind = classifySiteHref(href ?? "");
|
|
23816
23767
|
if (kind.kind === "internal") return /* @__PURE__ */ jsx("a", {
|
|
23817
23768
|
href: kind.to,
|
|
23818
23769
|
onClick: (event) => {
|
|
@@ -23966,15 +23917,6 @@ function starLadder(rows, multi) {
|
|
|
23966
23917
|
}
|
|
23967
23918
|
return byLevel.every((r) => r !== void 0) ? byLevel : null;
|
|
23968
23919
|
}
|
|
23969
|
-
/** The stars as a picture: `filled` of `max` lit. Same palette as the `f.rating` widget
|
|
23970
|
-
* (filled yellow, empty at border colour), so the gesture reads the same in both places. */
|
|
23971
|
-
function Stars({ filled, max, size }) {
|
|
23972
|
-
return /* @__PURE__ */ jsx(Fragment, { children: Array.from({ length: max }, (_, i) => /* @__PURE__ */ jsx(Star, {
|
|
23973
|
-
size,
|
|
23974
|
-
"aria-hidden": true,
|
|
23975
|
-
className: i < filled ? "shrink-0 fill-yellow-400 text-yellow-400" : "shrink-0 text-border"
|
|
23976
|
-
}, i)) });
|
|
23977
|
-
}
|
|
23978
23920
|
/** Flatten the catalog to one list: with a single registered agent the agent
|
|
23979
23921
|
* level is noise, and with several the agent name simply prefixes its presets. */
|
|
23980
23922
|
function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
|
|
@@ -24001,9 +23943,6 @@ function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
|
|
|
24001
23943
|
document.removeEventListener("keydown", onKeyDown);
|
|
24002
23944
|
};
|
|
24003
23945
|
}, [open]);
|
|
24004
|
-
useEffect(() => {
|
|
24005
|
-
if (!open) setPreview(null);
|
|
24006
|
-
}, [open]);
|
|
24007
23946
|
const multi = agents.length > 1;
|
|
24008
23947
|
const rows = agents.flatMap((a) => a.presets.map((p) => ({
|
|
24009
23948
|
agentId: a.id,
|
|
@@ -24017,43 +23956,19 @@ function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
|
|
|
24017
23956
|
if (rows.length === 0) return null;
|
|
24018
23957
|
const current = rows.find((r) => r.agentId === selection.agentId && r.presetId === selection.presetId) ?? rows.find((r) => r.agentId === defaultAgentId && r.isDefault) ?? rows.find((r) => r.isDefault) ?? rows[0];
|
|
24019
23958
|
const ladder = starLadder(rows, multi);
|
|
24020
|
-
|
|
24021
|
-
|
|
24022
|
-
|
|
24023
|
-
|
|
24024
|
-
|
|
24025
|
-
|
|
24026
|
-
|
|
24027
|
-
|
|
24028
|
-
|
|
24029
|
-
|
|
24030
|
-
children: [/* @__PURE__ */ jsx("button", {
|
|
24031
|
-
ref: triggerRef,
|
|
24032
|
-
type: "button",
|
|
24033
|
-
"aria-haspopup": "menu",
|
|
24034
|
-
"aria-expanded": open,
|
|
24035
|
-
className: "flex items-center gap-1 rounded-md px-2 py-1 text-xs text-muted hover:text-text focus-visible:outline-2 focus-visible:outline-accent",
|
|
24036
|
-
onClick: () => setOpen((v) => !v),
|
|
24037
|
-
...ladder ? { "aria-label": levelName(current, level) } : {},
|
|
24038
|
-
children: ladder ? /* @__PURE__ */ jsx("span", {
|
|
24039
|
-
className: "flex items-center gap-0.5",
|
|
24040
|
-
children: /* @__PURE__ */ jsx(Stars, {
|
|
24041
|
-
filled: level,
|
|
24042
|
-
max: ladder.length,
|
|
24043
|
-
size: 14
|
|
24044
|
-
})
|
|
24045
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
24046
|
-
className: "max-w-40 truncate",
|
|
24047
|
-
children: current.label
|
|
24048
|
-
}), /* @__PURE__ */ jsx(ChevronDown, {
|
|
24049
|
-
size: 12,
|
|
24050
|
-
className: "shrink-0"
|
|
24051
|
-
})] })
|
|
24052
|
-
}), open && (ladder ? /* @__PURE__ */ jsxs("div", {
|
|
24053
|
-
className: "absolute bottom-full left-0 z-10 mb-1 rounded-md border border-border bg-elevated px-2 py-1.5 shadow-lg",
|
|
23959
|
+
if (ladder) {
|
|
23960
|
+
const level = ladder.indexOf(current) + 1;
|
|
23961
|
+
const shown = preview ?? level;
|
|
23962
|
+
const previewRow = preview === null ? void 0 : ladder[preview - 1];
|
|
23963
|
+
const levelName = (row, n) => `${row.label} · ${t("webchat.level", {
|
|
23964
|
+
value: n,
|
|
23965
|
+
max: ladder.length
|
|
23966
|
+
})}`;
|
|
23967
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
23968
|
+
className: "flex min-w-0 items-center gap-1.5",
|
|
24054
23969
|
children: [/* @__PURE__ */ jsx("ul", {
|
|
24055
23970
|
"aria-label": t("webchat.agentPicker"),
|
|
24056
|
-
className: "flex items-center",
|
|
23971
|
+
className: "flex shrink-0 items-center",
|
|
24057
23972
|
onPointerLeave: () => setPreview(null),
|
|
24058
23973
|
children: ladder.map((r, i) => {
|
|
24059
23974
|
const n = i + 1;
|
|
@@ -24061,29 +23976,45 @@ function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
|
|
|
24061
23976
|
type: "button",
|
|
24062
23977
|
...r === current ? { "aria-current": true } : {},
|
|
24063
23978
|
"aria-label": levelName(r, n),
|
|
24064
|
-
className: "flex p-
|
|
23979
|
+
className: "flex rounded p-1 focus-visible:outline-2 focus-visible:outline-accent",
|
|
24065
23980
|
onPointerEnter: () => setPreview(n),
|
|
24066
23981
|
onFocus: () => setPreview(n),
|
|
24067
23982
|
onBlur: () => setPreview(null),
|
|
24068
|
-
onClick: () => {
|
|
24069
|
-
|
|
24070
|
-
|
|
24071
|
-
|
|
24072
|
-
presetId: r.presetId
|
|
24073
|
-
});
|
|
24074
|
-
},
|
|
23983
|
+
onClick: () => onChange({
|
|
23984
|
+
agentId: r.agentId,
|
|
23985
|
+
presetId: r.presetId
|
|
23986
|
+
}),
|
|
24075
23987
|
children: /* @__PURE__ */ jsx(Star, {
|
|
24076
|
-
size:
|
|
23988
|
+
size: 16,
|
|
24077
23989
|
"aria-hidden": true,
|
|
24078
23990
|
className: n <= shown ? "fill-yellow-400 text-yellow-400" : "text-border"
|
|
24079
23991
|
})
|
|
24080
23992
|
}) }, `${r.agentId}/${r.presetId}`);
|
|
24081
23993
|
})
|
|
24082
|
-
}), /* @__PURE__ */ jsx("
|
|
24083
|
-
className: "
|
|
24084
|
-
children:
|
|
23994
|
+
}), previewRow && /* @__PURE__ */ jsx("span", {
|
|
23995
|
+
className: "min-w-0 truncate text-xs text-muted",
|
|
23996
|
+
children: previewRow.hint || previewRow.label
|
|
24085
23997
|
})]
|
|
24086
|
-
})
|
|
23998
|
+
});
|
|
23999
|
+
}
|
|
24000
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
24001
|
+
className: "relative",
|
|
24002
|
+
ref: rootRef,
|
|
24003
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
24004
|
+
ref: triggerRef,
|
|
24005
|
+
type: "button",
|
|
24006
|
+
"aria-haspopup": "menu",
|
|
24007
|
+
"aria-expanded": open,
|
|
24008
|
+
className: "flex items-center gap-1 rounded-md px-2 py-1 text-xs text-muted hover:text-text focus-visible:outline-2 focus-visible:outline-accent",
|
|
24009
|
+
onClick: () => setOpen((v) => !v),
|
|
24010
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
24011
|
+
className: "max-w-40 truncate",
|
|
24012
|
+
children: current.label
|
|
24013
|
+
}), /* @__PURE__ */ jsx(ChevronDown, {
|
|
24014
|
+
size: 12,
|
|
24015
|
+
className: "shrink-0"
|
|
24016
|
+
})]
|
|
24017
|
+
}), open && /* @__PURE__ */ jsx("ul", {
|
|
24087
24018
|
"aria-label": t("webchat.agentPicker"),
|
|
24088
24019
|
className: "absolute bottom-full left-0 z-10 mb-1 max-h-64 w-64 max-w-[calc(100vw-2rem)] overflow-y-auto rounded-md border border-border bg-elevated py-1 shadow-lg",
|
|
24089
24020
|
children: rows.map((r) => {
|
|
@@ -24116,7 +24047,7 @@ function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
|
|
|
24116
24047
|
]
|
|
24117
24048
|
}) }, `${r.agentId}/${r.presetId}`);
|
|
24118
24049
|
})
|
|
24119
|
-
})
|
|
24050
|
+
})]
|
|
24120
24051
|
});
|
|
24121
24052
|
}
|
|
24122
24053
|
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export default definePlugin({
|
|
|
12
12
|
label: 'webchat.settings.reasoning_display',
|
|
13
13
|
default: 'live',
|
|
14
14
|
strict: true,
|
|
15
|
-
|
|
15
|
+
noSearch: true,
|
|
16
16
|
options: [
|
|
17
17
|
{ value: 'live', title: 'webchat.settings.reasoning_display_live' },
|
|
18
18
|
{ value: 'off', title: 'webchat.settings.reasoning_display_off' },
|
package/dist/runtime/format.d.ts
CHANGED
package/dist/runtime/format.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { frontendInstructions } from '@coffer-org/server/frontend-agent';
|
|
1
2
|
export const WEB_FORMAT = [
|
|
2
3
|
'CRITICAL OUTPUT FORMAT — MARKDOWN ONLY. Format the reply strictly as Markdown (GFM). NEVER emit raw HTML tags — they are stripped before rendering and their content may be lost.',
|
|
3
4
|
'Inline: **bold**, *italic*, `inline code`, ~~strikethrough~~. Links: [text](https://…).',
|
|
@@ -8,3 +9,7 @@ export const WEB_FORMAT = [
|
|
|
8
9
|
'The reply is read in a narrow chat panel (~360px) — keep it short and skimmable: short paragraphs, lists over prose, narrow tables.',
|
|
9
10
|
'Use Unicode symbols and emoji to structure replies: arrows (→), status marks (✅ ⚠️ ❌ ⏳), topic emoji (📅 💰 🏥 🛒 🌱 …).',
|
|
10
11
|
].join('\n');
|
|
12
|
+
export async function webChannelSystem() {
|
|
13
|
+
const site = await frontendInstructions('');
|
|
14
|
+
return [WEB_FORMAT, site].filter(Boolean).join('\n\n');
|
|
15
|
+
}
|
package/dist/runtime/send.js
CHANGED
|
@@ -3,7 +3,7 @@ import { handleIncoming, liveAgentId } from '@coffer-org/server/orchestrator';
|
|
|
3
3
|
import { chatIdFor, recordUser, buildChain, history, readSelectionForTurn } from "./chain-store.js";
|
|
4
4
|
import { policy } from "./config.js";
|
|
5
5
|
import { makeStreamingConnector } from "./connector.js";
|
|
6
|
-
import {
|
|
6
|
+
import { webChannelSystem } from "./format.js";
|
|
7
7
|
import { loadReasoningDisplay } from "./settings.js";
|
|
8
8
|
export function pageContext(ctx) {
|
|
9
9
|
if (typeof ctx !== 'object' || ctx === null)
|
|
@@ -47,11 +47,11 @@ export async function sendAction(body, ctx, deps = {}) {
|
|
|
47
47
|
emit: ctx.emit,
|
|
48
48
|
display: await loadReasoningDisplay(),
|
|
49
49
|
});
|
|
50
|
-
const
|
|
50
|
+
const turnContext = pageContext(body['context']);
|
|
51
51
|
await doHandle(connector, {
|
|
52
52
|
connectorId: 'webchat',
|
|
53
|
-
channelSystem:
|
|
54
|
-
...(
|
|
53
|
+
channelSystem: await webChannelSystem(),
|
|
54
|
+
...(turnContext ? { turnContext } : {}),
|
|
55
55
|
...(agentId ? { agentId } : {}),
|
|
56
56
|
...(selection.presetId ? { presetId: selection.presetId } : {}),
|
|
57
57
|
chatId,
|
package/dist/schema.js
CHANGED
|
@@ -4082,6 +4082,23 @@ function typeErr(code = "invalid_type") {
|
|
|
4082
4082
|
function reqTypeErr() {
|
|
4083
4083
|
return { error: (iss) => iss.code === "invalid_type" ? iss.input === void 0 ? vmsg("required") : vmsg("invalid_type") : void 0 };
|
|
4084
4084
|
}
|
|
4085
|
+
/**
|
|
4086
|
+
* A string schema whose CONTENT checks only speak once the value is known to be a string.
|
|
4087
|
+
*
|
|
4088
|
+
* zod's length checks are not type-guarded: `min`/`max` read `input.length`, and an ARRAY has
|
|
4089
|
+
* one. So `z.string().min(1)` answers `[]` with `invalid_type` AND `too_small` — two messages
|
|
4090
|
+
* about a single wrongness, both of which reach the user, since `mutate.ts` and `extend-io.ts`
|
|
4091
|
+
* turn every issue into its own `ValidationError`. (A number answers with `invalid_type`
|
|
4092
|
+
* alone, having no `length` to read — so the redundancy was array-shaped and invisible until
|
|
4093
|
+
* the golden matrix started recording the whole issue list instead of `issues[0]`.)
|
|
4094
|
+
*
|
|
4095
|
+
* Piping puts the type question first and alone: `add` never runs on a non-string. Pass the
|
|
4096
|
+
* content checks as a builder rather than chaining them onto the result, because a `ZodPipe`
|
|
4097
|
+
* has no `.min`/`.max`/`.regex` to chain.
|
|
4098
|
+
*/
|
|
4099
|
+
function stringContent(add) {
|
|
4100
|
+
return string$1(reqTypeErr()).pipe(add(string$1()));
|
|
4101
|
+
}
|
|
4085
4102
|
/** Parse a JSON string, else return the native value (object/array) as-is.
|
|
4086
4103
|
* Tolerant input for native form-state AND legacy JSON-string payloads.
|
|
4087
4104
|
* A string that fails JSON.parse is returned unchanged — callers detect
|
|
@@ -4709,18 +4726,36 @@ var UNITS_MAP = {
|
|
|
4709
4726
|
label: "core.units.ppm"
|
|
4710
4727
|
}]
|
|
4711
4728
|
};
|
|
4729
|
+
/**
|
|
4730
|
+
* Resolve a unit spec — a named scale or an explicit option list — to its options.
|
|
4731
|
+
*
|
|
4732
|
+
* Throws rather than returning `undefined`, because both ways of reaching `undefined` here
|
|
4733
|
+
* produce a field that CONSTRUCTS and then crashes when someone tries to save through it:
|
|
4734
|
+
* `measured`'s unit role closes over these options and calls `.some(…)` on them inside its
|
|
4735
|
+
* refine, so a missing spec or a typo'd scale name turns a user's save into a
|
|
4736
|
+
* `Cannot read properties of undefined` instead of a validation message. The golden matrix
|
|
4737
|
+
* recorded exactly that for `f.measured({})`.
|
|
4738
|
+
*
|
|
4739
|
+
* A declaration error belongs at declaration time, where the plugin author sees it and the
|
|
4740
|
+
* message can name the scale they meant — the same reason the host-services seam fails at
|
|
4741
|
+
* registration rather than at first render.
|
|
4742
|
+
*/
|
|
4712
4743
|
function resolveUnits(u) {
|
|
4713
|
-
|
|
4744
|
+
if (typeof u === "string") {
|
|
4745
|
+
const known = UNITS_MAP[u];
|
|
4746
|
+
if (!known) throw new Error(`units: unknown scale '${u}' (known: ${Object.keys(UNITS_MAP).sort().join(", ")})`);
|
|
4747
|
+
return known;
|
|
4748
|
+
}
|
|
4749
|
+
if (!Array.isArray(u)) throw new Error("units: a unit spec is required — a named scale, or an explicit list of options");
|
|
4750
|
+
return u;
|
|
4714
4751
|
}
|
|
4715
4752
|
//#endregion
|
|
4716
4753
|
//#region ../sdk/src/fields/normalize.ts
|
|
4717
4754
|
function normalizeOpts(rawIn) {
|
|
4718
4755
|
const raw = rawIn;
|
|
4756
|
+
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).");
|
|
4719
4757
|
const r = raw.rules ?? {};
|
|
4720
|
-
const v = {
|
|
4721
|
-
...raw.view ?? {},
|
|
4722
|
-
...raw.ui ?? {}
|
|
4723
|
-
};
|
|
4758
|
+
const v = raw.ui ?? {};
|
|
4724
4759
|
const config = {};
|
|
4725
4760
|
if (r.min != null && typeof r.min === "number") config.min = r.min;
|
|
4726
4761
|
if (r.max != null && typeof r.max === "number") config.max = r.max;
|
|
@@ -4729,7 +4764,7 @@ function normalizeOpts(rawIn) {
|
|
|
4729
4764
|
if (r.messageKey != null) config.messageKey = r.messageKey;
|
|
4730
4765
|
if (r.granularity != null) config.granularity = r.granularity;
|
|
4731
4766
|
if (r.language != null) config.language = r.language;
|
|
4732
|
-
const { rules: _r,
|
|
4767
|
+
const { rules: _r, ui: _ui, ...flat } = raw;
|
|
4733
4768
|
const result = {
|
|
4734
4769
|
...flat,
|
|
4735
4770
|
...Object.keys(config).length ? { config } : {},
|
|
@@ -4744,7 +4779,6 @@ function normalizeOpts(rawIn) {
|
|
|
4744
4779
|
step: r.step,
|
|
4745
4780
|
hidden: v.hidden,
|
|
4746
4781
|
noEditControl: v.noEditControl,
|
|
4747
|
-
noSearch: v.noSearch,
|
|
4748
4782
|
display: v.display,
|
|
4749
4783
|
kind: v.kind,
|
|
4750
4784
|
icon: v.icon,
|
|
@@ -4752,7 +4786,11 @@ function normalizeOpts(rawIn) {
|
|
|
4752
4786
|
...v.keyLabel !== void 0 ? { keyLabel: v.keyLabel } : {},
|
|
4753
4787
|
...v.valueLabel !== void 0 ? { valueLabel: v.valueLabel } : {},
|
|
4754
4788
|
compareWith: v.compareWith,
|
|
4755
|
-
span: v.span
|
|
4789
|
+
span: v.span,
|
|
4790
|
+
emphasis: v.emphasis,
|
|
4791
|
+
noLabel: v.noLabel,
|
|
4792
|
+
role: v.role,
|
|
4793
|
+
editor: v.editor
|
|
4756
4794
|
};
|
|
4757
4795
|
return Object.fromEntries(Object.entries(result).filter(([, val]) => val !== void 0));
|
|
4758
4796
|
}
|
|
@@ -5002,7 +5040,7 @@ function internalApiToken(o) {
|
|
|
5002
5040
|
label: o.label,
|
|
5003
5041
|
multiple: true,
|
|
5004
5042
|
rules: { unique: ["name"] },
|
|
5005
|
-
|
|
5043
|
+
ui: { kind: "internalApiToken" },
|
|
5006
5044
|
fields: {
|
|
5007
5045
|
name: string({}),
|
|
5008
5046
|
token: password({}),
|
|
@@ -5028,7 +5066,7 @@ function internalOauthGrants(o) {
|
|
|
5028
5066
|
scope: "nest",
|
|
5029
5067
|
label: o.label,
|
|
5030
5068
|
multiple: true,
|
|
5031
|
-
|
|
5069
|
+
ui: { kind: "internalOauthGrants" },
|
|
5032
5070
|
fields: {
|
|
5033
5071
|
clientName: string({}),
|
|
5034
5072
|
createdAt: string({}),
|
|
@@ -5655,10 +5693,8 @@ function country(raw) {
|
|
|
5655
5693
|
...raw,
|
|
5656
5694
|
options: "countries",
|
|
5657
5695
|
strict: true,
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
noSearch: true
|
|
5661
|
-
}
|
|
5696
|
+
ui: { ...raw.ui ?? {} },
|
|
5697
|
+
noSearch: true
|
|
5662
5698
|
});
|
|
5663
5699
|
}
|
|
5664
5700
|
/**
|
|
@@ -5682,10 +5718,8 @@ function currency(raw) {
|
|
|
5682
5718
|
...raw,
|
|
5683
5719
|
options: "currencies",
|
|
5684
5720
|
strict: true,
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
noSearch: true
|
|
5688
|
-
}
|
|
5721
|
+
ui: { ...raw.ui ?? {} },
|
|
5722
|
+
noSearch: true
|
|
5689
5723
|
});
|
|
5690
5724
|
}
|
|
5691
5725
|
/**
|
|
@@ -5845,7 +5879,7 @@ function bento(opts) {
|
|
|
5845
5879
|
label: opts.label,
|
|
5846
5880
|
icon: opts.icon,
|
|
5847
5881
|
fields,
|
|
5848
|
-
|
|
5882
|
+
ui: { kind: "bento" }
|
|
5849
5883
|
}),
|
|
5850
5884
|
view: {
|
|
5851
5885
|
kind: "bento",
|
|
@@ -5874,7 +5908,7 @@ function split(opts) {
|
|
|
5874
5908
|
return {
|
|
5875
5909
|
...group({
|
|
5876
5910
|
fields: merge("split", opts.columns[0], opts.columns[1]),
|
|
5877
|
-
|
|
5911
|
+
ui: { kind: "split" }
|
|
5878
5912
|
}),
|
|
5879
5913
|
view: {
|
|
5880
5914
|
kind: "split",
|
|
@@ -5898,7 +5932,7 @@ function grid(opts) {
|
|
|
5898
5932
|
label: opts.label,
|
|
5899
5933
|
icon: opts.icon,
|
|
5900
5934
|
fields: opts.fields,
|
|
5901
|
-
|
|
5935
|
+
ui: { kind: "grid" }
|
|
5902
5936
|
}),
|
|
5903
5937
|
view: {
|
|
5904
5938
|
kind: "grid",
|
|
@@ -5923,7 +5957,7 @@ function spread(opts) {
|
|
|
5923
5957
|
return {
|
|
5924
5958
|
...group({
|
|
5925
5959
|
fields: merge("spread", visual, quote, opts.facts),
|
|
5926
|
-
|
|
5960
|
+
ui: { kind: "spread" }
|
|
5927
5961
|
}),
|
|
5928
5962
|
view: {
|
|
5929
5963
|
kind: "spread",
|
|
@@ -5948,7 +5982,7 @@ function aside(opts) {
|
|
|
5948
5982
|
return {
|
|
5949
5983
|
...group({
|
|
5950
5984
|
fields: merge("aside", opts.fields, opts.aside),
|
|
5951
|
-
|
|
5985
|
+
ui: { kind: "aside" }
|
|
5952
5986
|
}),
|
|
5953
5987
|
view: {
|
|
5954
5988
|
kind: "aside",
|
|
@@ -5980,7 +6014,7 @@ function tabs(opts) {
|
|
|
5980
6014
|
return {
|
|
5981
6015
|
...group({
|
|
5982
6016
|
fields,
|
|
5983
|
-
|
|
6017
|
+
ui: { kind: "tabs" }
|
|
5984
6018
|
}),
|
|
5985
6019
|
view: {
|
|
5986
6020
|
kind: "tabs",
|
|
@@ -6012,7 +6046,7 @@ function accordion(opts) {
|
|
|
6012
6046
|
return {
|
|
6013
6047
|
...group({
|
|
6014
6048
|
fields,
|
|
6015
|
-
|
|
6049
|
+
ui: { kind: "accordion" }
|
|
6016
6050
|
}),
|
|
6017
6051
|
view: {
|
|
6018
6052
|
kind: "accordion",
|
|
@@ -6042,7 +6076,7 @@ function prose(opts) {
|
|
|
6042
6076
|
label: opts.label,
|
|
6043
6077
|
icon: opts.icon,
|
|
6044
6078
|
fields: opts.fields,
|
|
6045
|
-
|
|
6079
|
+
ui: { kind: "prose" }
|
|
6046
6080
|
}),
|
|
6047
6081
|
view: {
|
|
6048
6082
|
kind: "prose",
|
|
@@ -6068,7 +6102,7 @@ function plaque(opts) {
|
|
|
6068
6102
|
label: opts.label,
|
|
6069
6103
|
icon: opts.icon,
|
|
6070
6104
|
fields: merge("plaque", title, opts.subtitle),
|
|
6071
|
-
|
|
6105
|
+
ui: { kind: "plaque" }
|
|
6072
6106
|
}),
|
|
6073
6107
|
view: {
|
|
6074
6108
|
kind: "plaque",
|
|
@@ -6091,7 +6125,7 @@ function ledger(opts) {
|
|
|
6091
6125
|
label: opts.label,
|
|
6092
6126
|
icon: opts.icon,
|
|
6093
6127
|
fields: opts.fields,
|
|
6094
|
-
|
|
6128
|
+
ui: { kind: "ledger" }
|
|
6095
6129
|
}),
|
|
6096
6130
|
view: { kind: "ledger" }
|
|
6097
6131
|
};
|
|
@@ -6110,7 +6144,7 @@ function stats(opts) {
|
|
|
6110
6144
|
label: opts.label,
|
|
6111
6145
|
icon: opts.icon,
|
|
6112
6146
|
fields: opts.fields,
|
|
6113
|
-
|
|
6147
|
+
ui: { kind: "stats" }
|
|
6114
6148
|
}),
|
|
6115
6149
|
view: { kind: "stats" }
|
|
6116
6150
|
};
|
|
@@ -6129,7 +6163,7 @@ function facets(opts) {
|
|
|
6129
6163
|
label: opts.label,
|
|
6130
6164
|
icon: opts.icon,
|
|
6131
6165
|
fields: opts.fields,
|
|
6132
|
-
|
|
6166
|
+
ui: { kind: "facets" }
|
|
6133
6167
|
}),
|
|
6134
6168
|
view: { kind: "facets" }
|
|
6135
6169
|
};
|
|
@@ -6148,7 +6182,7 @@ function terminal(opts) {
|
|
|
6148
6182
|
label: opts.label,
|
|
6149
6183
|
icon: opts.icon,
|
|
6150
6184
|
fields: opts.fields,
|
|
6151
|
-
|
|
6185
|
+
ui: { kind: "terminal" }
|
|
6152
6186
|
}),
|
|
6153
6187
|
view: {
|
|
6154
6188
|
kind: "terminal",
|
|
@@ -6170,7 +6204,7 @@ function callout(opts) {
|
|
|
6170
6204
|
label: opts.label,
|
|
6171
6205
|
icon: opts.icon,
|
|
6172
6206
|
fields: opts.fields,
|
|
6173
|
-
|
|
6207
|
+
ui: { kind: "callout" }
|
|
6174
6208
|
}),
|
|
6175
6209
|
view: {
|
|
6176
6210
|
kind: "callout",
|
|
@@ -6195,7 +6229,7 @@ function compare(opts) {
|
|
|
6195
6229
|
label: opts.label,
|
|
6196
6230
|
icon: opts.icon,
|
|
6197
6231
|
fields: merge("compare", opts.left, opts.right),
|
|
6198
|
-
|
|
6232
|
+
ui: { kind: "compare" }
|
|
6199
6233
|
}),
|
|
6200
6234
|
view: {
|
|
6201
6235
|
kind: "compare",
|
|
@@ -6226,7 +6260,7 @@ function figure(opts) {
|
|
|
6226
6260
|
return {
|
|
6227
6261
|
...group({
|
|
6228
6262
|
fields: merge("figure", image, caption),
|
|
6229
|
-
|
|
6263
|
+
ui: { kind: "figure" }
|
|
6230
6264
|
}),
|
|
6231
6265
|
view: {
|
|
6232
6266
|
kind: "figure",
|
|
@@ -6251,7 +6285,7 @@ function epigraph(opts) {
|
|
|
6251
6285
|
return {
|
|
6252
6286
|
...group({
|
|
6253
6287
|
fields: merge("epigraph", source, attribution),
|
|
6254
|
-
|
|
6288
|
+
ui: { kind: "epigraph" }
|
|
6255
6289
|
}),
|
|
6256
6290
|
view: {
|
|
6257
6291
|
kind: "epigraph",
|
|
@@ -6277,7 +6311,7 @@ function masthead(opts) {
|
|
|
6277
6311
|
return {
|
|
6278
6312
|
...group({
|
|
6279
6313
|
fields: merge("masthead", overline, title, opts.meta),
|
|
6280
|
-
|
|
6314
|
+
ui: { kind: "masthead" }
|
|
6281
6315
|
}),
|
|
6282
6316
|
view: {
|
|
6283
6317
|
kind: "masthead",
|
|
@@ -6301,7 +6335,7 @@ function timeline(opts) {
|
|
|
6301
6335
|
label: opts.label,
|
|
6302
6336
|
icon: opts.icon,
|
|
6303
6337
|
fields: opts.fields,
|
|
6304
|
-
|
|
6338
|
+
ui: { kind: "timeline" }
|
|
6305
6339
|
}),
|
|
6306
6340
|
view: { kind: "timeline" }
|
|
6307
6341
|
};
|
|
@@ -6320,7 +6354,7 @@ function countdown(opts) {
|
|
|
6320
6354
|
...group({
|
|
6321
6355
|
label: opts.label,
|
|
6322
6356
|
fields: source,
|
|
6323
|
-
|
|
6357
|
+
ui: { kind: "countdown" }
|
|
6324
6358
|
}),
|
|
6325
6359
|
view: {
|
|
6326
6360
|
kind: "countdown",
|
|
@@ -6343,7 +6377,7 @@ function deck(opts) {
|
|
|
6343
6377
|
...group({
|
|
6344
6378
|
label: opts.label,
|
|
6345
6379
|
fields: source,
|
|
6346
|
-
|
|
6380
|
+
ui: { kind: "deck" }
|
|
6347
6381
|
}),
|
|
6348
6382
|
view: {
|
|
6349
6383
|
kind: "deck",
|
|
@@ -6365,7 +6399,7 @@ function people(opts) {
|
|
|
6365
6399
|
...group({
|
|
6366
6400
|
label: opts.label,
|
|
6367
6401
|
fields: source,
|
|
6368
|
-
|
|
6402
|
+
ui: { kind: "people" }
|
|
6369
6403
|
}),
|
|
6370
6404
|
view: {
|
|
6371
6405
|
kind: "people",
|
|
@@ -6391,7 +6425,7 @@ function identity(opts) {
|
|
|
6391
6425
|
return {
|
|
6392
6426
|
...group({
|
|
6393
6427
|
fields: merge("identity", avatar, name, role, opts.channels),
|
|
6394
|
-
|
|
6428
|
+
ui: { kind: "identity" }
|
|
6395
6429
|
}),
|
|
6396
6430
|
view: {
|
|
6397
6431
|
kind: "identity",
|
|
@@ -6417,7 +6451,7 @@ function score(opts) {
|
|
|
6417
6451
|
...group({
|
|
6418
6452
|
label: opts.label,
|
|
6419
6453
|
fields: merge("score", source, verdict),
|
|
6420
|
-
|
|
6454
|
+
ui: { kind: "score" }
|
|
6421
6455
|
}),
|
|
6422
6456
|
view: {
|
|
6423
6457
|
kind: "score",
|
|
@@ -6442,7 +6476,7 @@ function status(opts) {
|
|
|
6442
6476
|
...group({
|
|
6443
6477
|
label: opts.label,
|
|
6444
6478
|
fields: merge("status", source, since),
|
|
6445
|
-
|
|
6479
|
+
ui: { kind: "status" }
|
|
6446
6480
|
}),
|
|
6447
6481
|
view: {
|
|
6448
6482
|
kind: "status",
|
|
@@ -6466,7 +6500,7 @@ function meter(opts) {
|
|
|
6466
6500
|
...group({
|
|
6467
6501
|
label: opts.label,
|
|
6468
6502
|
fields: merge("meter", source, of),
|
|
6469
|
-
|
|
6503
|
+
ui: { kind: "meter" }
|
|
6470
6504
|
}),
|
|
6471
6505
|
view: {
|
|
6472
6506
|
kind: "meter",
|
|
@@ -6493,7 +6527,7 @@ function receipt(opts) {
|
|
|
6493
6527
|
...group({
|
|
6494
6528
|
label: opts.label,
|
|
6495
6529
|
fields: merge("receipt", opts.fields, total),
|
|
6496
|
-
|
|
6530
|
+
ui: { kind: "receipt" }
|
|
6497
6531
|
}),
|
|
6498
6532
|
view: {
|
|
6499
6533
|
kind: "receipt",
|
|
@@ -6516,7 +6550,7 @@ function balance(opts) {
|
|
|
6516
6550
|
...group({
|
|
6517
6551
|
label: opts.label,
|
|
6518
6552
|
fields: source,
|
|
6519
|
-
|
|
6553
|
+
ui: { kind: "balance" }
|
|
6520
6554
|
}),
|
|
6521
6555
|
view: {
|
|
6522
6556
|
kind: "balance",
|
|
@@ -6544,7 +6578,7 @@ function route(opts) {
|
|
|
6544
6578
|
return {
|
|
6545
6579
|
...group({
|
|
6546
6580
|
fields: merge("route", from, to, depart, arrive, duration),
|
|
6547
|
-
|
|
6581
|
+
ui: { kind: "route" }
|
|
6548
6582
|
}),
|
|
6549
6583
|
view: {
|
|
6550
6584
|
kind: "route",
|
|
@@ -6693,7 +6727,7 @@ function nutrition(opts) {
|
|
|
6693
6727
|
...group({
|
|
6694
6728
|
label: opts.label,
|
|
6695
6729
|
fields: merge("nutrition", energy, opts.fields),
|
|
6696
|
-
|
|
6730
|
+
ui: { kind: "nutrition" }
|
|
6697
6731
|
}),
|
|
6698
6732
|
view: {
|
|
6699
6733
|
kind: "nutrition",
|
|
@@ -6719,7 +6753,7 @@ function specimen(opts) {
|
|
|
6719
6753
|
return {
|
|
6720
6754
|
...group({
|
|
6721
6755
|
fields: merge("specimen", title, subtitle, opts.conditions),
|
|
6722
|
-
|
|
6756
|
+
ui: { kind: "specimen" }
|
|
6723
6757
|
}),
|
|
6724
6758
|
view: {
|
|
6725
6759
|
kind: "specimen",
|
|
@@ -6796,7 +6830,7 @@ function idcard(opts) {
|
|
|
6796
6830
|
return {
|
|
6797
6831
|
...group({
|
|
6798
6832
|
fields: merge("idcard", overline, number, opts.meta),
|
|
6799
|
-
|
|
6833
|
+
ui: { kind: "idcard" }
|
|
6800
6834
|
}),
|
|
6801
6835
|
view: {
|
|
6802
6836
|
kind: "idcard",
|
|
@@ -6822,7 +6856,7 @@ function properties(opts) {
|
|
|
6822
6856
|
label: opts.label,
|
|
6823
6857
|
icon: opts.icon,
|
|
6824
6858
|
fields: opts.fields,
|
|
6825
|
-
|
|
6859
|
+
ui: { kind: "properties" }
|
|
6826
6860
|
}),
|
|
6827
6861
|
view: {
|
|
6828
6862
|
kind: "properties",
|
|
@@ -6844,7 +6878,7 @@ function stack(opts) {
|
|
|
6844
6878
|
label: opts.label,
|
|
6845
6879
|
icon: opts.icon,
|
|
6846
6880
|
fields: opts.fields,
|
|
6847
|
-
|
|
6881
|
+
ui: { kind: "stack" }
|
|
6848
6882
|
}),
|
|
6849
6883
|
view: { kind: "stack" }
|
|
6850
6884
|
};
|
|
@@ -7725,8 +7759,9 @@ var hasChildren = (x) => Array.isArray(x.fields);
|
|
|
7725
7759
|
* @example f.group({ scope: 'nest', label: 'mod.fields.contact', fields: { name: f.string({}) } })
|
|
7726
7760
|
*/
|
|
7727
7761
|
function group(o) {
|
|
7762
|
+
if (o["view"] !== void 0) throw new Error("[group] `view` is gone — presentational options live in `ui: {}` (see CLAUDE.md § field author options and the field-types skill).");
|
|
7728
7763
|
const r = o.rules ?? {};
|
|
7729
|
-
const v = o.
|
|
7764
|
+
const v = o.ui ?? {};
|
|
7730
7765
|
const fields = materializeTree(o.fields);
|
|
7731
7766
|
const scope = o.scope ?? "hoist";
|
|
7732
7767
|
if (scope === "nest") {
|
|
@@ -7772,7 +7807,7 @@ function row(o) {
|
|
|
7772
7807
|
multiple: o.multiple,
|
|
7773
7808
|
required: o.required,
|
|
7774
7809
|
rules: o.rules,
|
|
7775
|
-
|
|
7810
|
+
ui: { display: "scroll" }
|
|
7776
7811
|
});
|
|
7777
7812
|
}
|
|
7778
7813
|
/**
|
|
@@ -7800,7 +7835,7 @@ function sheet(o) {
|
|
|
7800
7835
|
label: o.label,
|
|
7801
7836
|
icon: o.icon,
|
|
7802
7837
|
fields: rows,
|
|
7803
|
-
|
|
7838
|
+
ui: { display: "sheet" }
|
|
7804
7839
|
});
|
|
7805
7840
|
}
|
|
7806
7841
|
/**
|
|
@@ -7819,7 +7854,7 @@ function url(o) {
|
|
|
7819
7854
|
scope: "nest",
|
|
7820
7855
|
label: o.label,
|
|
7821
7856
|
required: o.required,
|
|
7822
|
-
|
|
7857
|
+
ui: { kind: "url" },
|
|
7823
7858
|
fields: {
|
|
7824
7859
|
scheme: string({}),
|
|
7825
7860
|
username: string({}),
|
|
@@ -7961,13 +7996,15 @@ registerType("string", {
|
|
|
7961
7996
|
build(o) {
|
|
7962
7997
|
const cfg = o.config ?? {};
|
|
7963
7998
|
const min = cfg.min ?? (o.required === true ? 1 : 0);
|
|
7964
|
-
let s = string$1(reqTypeErr());
|
|
7965
|
-
if (min > 0) s = s.min(min, { message: vmsg("min_length", { min }) });
|
|
7966
|
-
if (cfg.max != null) s = s.max(cfg.max, { message: vmsg("max_length", { max: cfg.max }) });
|
|
7967
|
-
if (cfg.pattern) s = s.regex(new RegExp(cfg.pattern), { message: vmsg("pattern", cfg.messageKey ? { messageKey: cfg.messageKey } : void 0) });
|
|
7968
7999
|
return {
|
|
7969
8000
|
column: "text",
|
|
7970
|
-
zod:
|
|
8001
|
+
zod: stringContent((str) => {
|
|
8002
|
+
let c = str;
|
|
8003
|
+
if (min > 0) c = c.min(min, { message: vmsg("min_length", { min }) });
|
|
8004
|
+
if (cfg.max != null) c = c.max(cfg.max, { message: vmsg("max_length", { max: cfg.max }) });
|
|
8005
|
+
if (cfg.pattern) c = c.regex(new RegExp(cfg.pattern), { message: vmsg("pattern", cfg.messageKey ? { messageKey: cfg.messageKey } : void 0) });
|
|
8006
|
+
return c;
|
|
8007
|
+
}),
|
|
7971
8008
|
hints: {
|
|
7972
8009
|
minLength: cfg.min,
|
|
7973
8010
|
maxLength: cfg.max
|
|
@@ -7988,9 +8025,7 @@ function string(o) {
|
|
|
7988
8025
|
}
|
|
7989
8026
|
/** Shared by localDir/localFile's build(): a server path, min-length-1 when required. */
|
|
7990
8027
|
function pathZod(o) {
|
|
7991
|
-
|
|
7992
|
-
if (o.required === true) s = s.min(1, { message: vmsg("min_length", { min: 1 }) });
|
|
7993
|
-
return s;
|
|
8028
|
+
return stringContent((s) => o.required === true ? s.min(1, { message: vmsg("min_length", { min: 1 }) }) : s);
|
|
7994
8029
|
}
|
|
7995
8030
|
registerType("localDir", {
|
|
7996
8031
|
kind: "localDir",
|
|
@@ -8652,7 +8687,8 @@ var MEASURED_ROLES = (opts) => {
|
|
|
8652
8687
|
label,
|
|
8653
8688
|
required,
|
|
8654
8689
|
value,
|
|
8655
|
-
|
|
8690
|
+
noSearch: d?.opts.noSearch,
|
|
8691
|
+
ui: uiOf(d?.opts),
|
|
8656
8692
|
options: opts.units,
|
|
8657
8693
|
strict: true
|
|
8658
8694
|
});
|
|
@@ -9021,7 +9057,7 @@ function periodMkSubDecl(granularity) {
|
|
|
9021
9057
|
* (`mkSubDecl`), so it writes its own `normalize` rather than using `role()`. A CORRECTED
|
|
9022
9058
|
* filling still keeps the composite's own granularity (`canonical.opts`) — the same
|
|
9023
9059
|
* `date`/`datetime` config the role's own default carries — with the author's
|
|
9024
|
-
* label/required/
|
|
9060
|
+
* label/required/ui/value layered on top, exactly like `retypeTo`'s default.
|
|
9025
9061
|
*/
|
|
9026
9062
|
function periodRole(key, label, mkSubDecl) {
|
|
9027
9063
|
const def = {
|
|
@@ -9037,7 +9073,8 @@ function periodRole(key, label, mkSubDecl) {
|
|
|
9037
9073
|
label: l ?? canonical.opts.label,
|
|
9038
9074
|
required,
|
|
9039
9075
|
value,
|
|
9040
|
-
|
|
9076
|
+
noSearch: d.opts.noSearch,
|
|
9077
|
+
ui: uiOf(d.opts)
|
|
9041
9078
|
});
|
|
9042
9079
|
}
|
|
9043
9080
|
};
|
|
@@ -9542,15 +9579,14 @@ function toClient(field) {
|
|
|
9542
9579
|
* open: a magnitude may be real/int/rating, a unit may be a select or a relation.
|
|
9543
9580
|
*/
|
|
9544
9581
|
/**
|
|
9545
|
-
* `NormalizedOpts` (what `FieldDecl.opts` actually is) has no single `
|
|
9546
|
-
* author's `
|
|
9547
|
-
* by `normalizeOpts`. A correction that wants to carry
|
|
9548
|
-
* flat fields back into the nested shape `declare()`'s own `normalizeOpts` call expects.
|
|
9582
|
+
* `NormalizedOpts` (what `FieldDecl.opts` actually is) has no single `ui` property — the
|
|
9583
|
+
* author's `ui` is flattened into individual fields (`hidden`, `display`, `kind`, …)
|
|
9584
|
+
* by `normalizeOpts`. A correction that wants to carry presentation forward has to collect
|
|
9585
|
+
* those flat fields back into the nested shape `declare()`'s own `normalizeOpts` call expects.
|
|
9549
9586
|
*/
|
|
9550
|
-
var
|
|
9587
|
+
var UI_KEYS = [
|
|
9551
9588
|
"hidden",
|
|
9552
9589
|
"noEditControl",
|
|
9553
|
-
"noSearch",
|
|
9554
9590
|
"display",
|
|
9555
9591
|
"kind",
|
|
9556
9592
|
"icon",
|
|
@@ -9561,11 +9597,11 @@ var VIEW_KEYS = [
|
|
|
9561
9597
|
"span"
|
|
9562
9598
|
];
|
|
9563
9599
|
/** The author's presentation opts, re-nested from `opts`'s flat fields — `undefined` when
|
|
9564
|
-
* none were set, so a correction never adds an empty `
|
|
9565
|
-
function
|
|
9600
|
+
* none were set, so a correction never adds an empty `ui: {}` no filling ever had. */
|
|
9601
|
+
function uiOf(opts) {
|
|
9566
9602
|
if (!opts) return void 0;
|
|
9567
9603
|
const v = {};
|
|
9568
|
-
for (const k of
|
|
9604
|
+
for (const k of UI_KEYS) if (opts[k] !== void 0) v[k] = opts[k];
|
|
9569
9605
|
return Object.keys(v).length ? v : void 0;
|
|
9570
9606
|
}
|
|
9571
9607
|
/** A filling built from a ported factory arrives as a FieldDecl — build it into the
|
|
@@ -9588,7 +9624,7 @@ function accepts(def, d) {
|
|
|
9588
9624
|
}
|
|
9589
9625
|
/**
|
|
9590
9626
|
* The correction every role gets for free: keep what the author said ABOUT the field
|
|
9591
|
-
* (`label`, `required`, `
|
|
9627
|
+
* (`label`, `required`, `ui`, `value` — a constant or a ComputeFn), replace the type, and
|
|
9592
9628
|
* drop what belonged to the OLD type (`rules` — a number's min/max mean nothing to a
|
|
9593
9629
|
* picture — and `multiple`, because a composite role owns exactly one column). A role whose
|
|
9594
9630
|
* own default needs more than a bare factory (`measured`'s unit, `period`'s endpoints)
|
|
@@ -9601,7 +9637,8 @@ function retypeTo(factory) {
|
|
|
9601
9637
|
label,
|
|
9602
9638
|
required,
|
|
9603
9639
|
value,
|
|
9604
|
-
|
|
9640
|
+
noSearch: d?.opts.noSearch,
|
|
9641
|
+
ui: uiOf(d?.opts)
|
|
9605
9642
|
});
|
|
9606
9643
|
};
|
|
9607
9644
|
}
|
|
@@ -9784,56 +9821,13 @@ function partColumns(parts) {
|
|
|
9784
9821
|
}
|
|
9785
9822
|
return cols;
|
|
9786
9823
|
}
|
|
9787
|
-
|
|
9788
|
-
* A composite's write-time row shape, DERIVED from its resolved parts. `shape` carries
|
|
9789
|
-
* the composite's DEFAULT schema per role — the literal the factory writes by hand
|
|
9790
|
-
* (`{ value: numSchema, unit: z.string().refine(…) }`) — and each part decides which
|
|
9791
|
-
* schema guards its (role-named) key:
|
|
9792
|
-
*
|
|
9793
|
-
* - a plain STORED part is keyed by its role name, matching the value object and the
|
|
9794
|
-
* column `<field>__<role>` that `flattenEmbeddedAt`/`nestEmbeddedAt` read. A role left
|
|
9795
|
-
* to its default filling keeps the default schema — byte-identical to the hand-written
|
|
9796
|
-
* literal, so every existing structure/unit/range code and message is preserved.
|
|
9797
|
-
* - an OVERRIDDEN stored part is validated by its OWN zod (`meta.zod`) instead of the
|
|
9798
|
-
* default schema for that role. This is what makes a RETYPED filling real: an
|
|
9799
|
-
* `f.int({})` magnitude rejects 5.5 (its own `int` check) and gets an integer column,
|
|
9800
|
-
* and an `f.relation({…})` currency accepts a record id instead of being measured
|
|
9801
|
-
* against an ISO-4217 string rule it was never meant to satisfy. The filling also
|
|
9802
|
-
* brings its own optionality and its own min/max — an author replacing a filling
|
|
9803
|
-
* takes over that role's constraints, so `f.real({ rules: { min: 0 } })` is how a
|
|
9804
|
-
* replaced magnitude keeps a bound.
|
|
9805
|
-
* - a COMPUTED-AND-STORED part ('computedStored') is keyed by its role name like any
|
|
9806
|
-
* other part, but its schema is `z.unknown().optional()`: the SERVER owns the value, so
|
|
9807
|
-
* whatever the client sends there is ignored — `stripServerOwnedParts` deletes it a
|
|
9808
|
-
* moment later and the write path recomputes the column. It is stripped rather than
|
|
9809
|
-
* `z.never()`-rejected because a read hands the client that key (it is a real column,
|
|
9810
|
-
* present in every response), so rejecting it would break a read → PATCH-the-whole-object
|
|
9811
|
-
* round trip.
|
|
9812
|
-
* - a CLIENT-OWNED stored part of a composite that is not `required: true` also accepts
|
|
9813
|
-
* an explicit `null` (`tolerateNull`), which is what closes the read → write round trip
|
|
9814
|
-
* for a PARTIALLY FILLED composite. A SELECT returns every sub-column, so a record whose
|
|
9815
|
-
* magnitude column is set and whose unit column is empty reads back as
|
|
9816
|
-
* `{value: 900, unit: null}` (`nestEmbeddedAt` drops the key only when EVERY column is
|
|
9817
|
-
* empty — `0` is a value), and feeding that exact object back used to fail with
|
|
9818
|
-
* `measured_structure`: the write path refused the shape its own read produced. The null
|
|
9819
|
-
* slot now parses through and is written back as NULL, so the round trip is closed and
|
|
9820
|
-
* clearing ONE role of a filled composite works instead of erroring.
|
|
9821
|
-
* NULL, not absence: a read emits every stored role (empty ones as `null`), so `null` is
|
|
9822
|
-
* how "this record has no value there" arrives, while an ABSENT key is a writer that
|
|
9823
|
-
* never mentioned a role it owns — which stays the structure error it has always been
|
|
9824
|
-
* (`{unit: 'kg'}` with no magnitude, `{lat: 50}` with no longitude). That is the
|
|
9825
|
-
* all-or-nothing rule for a DECLARATION, and it is untouched.
|
|
9826
|
-
* A `required: true` composite keeps every client-owned slot MANDATORY, null included —
|
|
9827
|
-
* "tolerate null" must not turn a required field optional, so an empty or half-empty
|
|
9828
|
-
* value still fails with the same structure code it fails with today. A filling the
|
|
9829
|
-
* author declared `required` keeps its own mandate too.
|
|
9830
|
-
*/
|
|
9831
|
-
function partsRowShape(shape, parts, tolerateNull = false) {
|
|
9824
|
+
function partsRowShape(shape, parts, tolerance = "strict") {
|
|
9832
9825
|
const out = { ...shape };
|
|
9833
9826
|
for (const p of parts) if (p.mode === "computedStored") out[p.key] = unknown().optional();
|
|
9834
9827
|
else {
|
|
9835
9828
|
const own = p.overridden ? p.meta.zod : shape[p.role] ?? unknown().optional();
|
|
9836
|
-
|
|
9829
|
+
const relaxed = tolerance !== "strict" && !(p.overridden && p.meta.required === true);
|
|
9830
|
+
out[p.key] = relaxed ? tolerance === "null-or-missing" ? own.nullish() : own.nullable() : own;
|
|
9837
9831
|
}
|
|
9838
9832
|
return out;
|
|
9839
9833
|
}
|
|
@@ -9874,16 +9868,17 @@ function stripServerOwnedParts(value, parts) {
|
|
|
9874
9868
|
function buildComposite(def, opts, parts) {
|
|
9875
9869
|
const required = opts.required ?? false;
|
|
9876
9870
|
const roleZod = def.roleZod(opts, parts);
|
|
9877
|
-
const rowSchema = object(partsRowShape(roleZod, parts, required
|
|
9878
|
-
const absentRowSchema = required === true ? object(partsRowShape(roleZod, parts,
|
|
9871
|
+
const rowSchema = object(partsRowShape(roleZod, parts, required === true ? "strict" : "null"));
|
|
9872
|
+
const absentRowSchema = required === true ? object(partsRowShape(roleZod, parts, "null-or-missing")) : rowSchema;
|
|
9879
9873
|
const roleKeys = (which) => parts.filter((p) => p.mode === "stored" && (which?.(p) ?? true)).map(partValueKey);
|
|
9880
9874
|
const absenceKeys = def.absenceRoles ? roleKeys((p) => def.absenceRoles.includes(p.role)) : void 0;
|
|
9881
9875
|
const clientKeys = def.absenceRoles ? roleKeys() : void 0;
|
|
9882
9876
|
const zod = unknown().transform((raw, ctx) => {
|
|
9883
9877
|
const parsed = jsonValue(raw);
|
|
9884
9878
|
const p = parsed;
|
|
9879
|
+
const isRow = p == null || typeof p === "object" && !Array.isArray(p);
|
|
9885
9880
|
/** Every one of `keys` empty in this value — `null`/absent alike. */
|
|
9886
|
-
const allEmpty = (keys) => p == null ||
|
|
9881
|
+
const allEmpty = (keys) => isRow && (p == null || keys.length > 0 && keys.every((k) => p[k] == null));
|
|
9887
9882
|
let absent = false;
|
|
9888
9883
|
if (absenceKeys && clientKeys) {
|
|
9889
9884
|
absent = allEmpty(absenceKeys);
|
|
@@ -10132,7 +10127,7 @@ var src_default = definePlugin({
|
|
|
10132
10127
|
label: "webchat.settings.reasoning_display",
|
|
10133
10128
|
default: "live",
|
|
10134
10129
|
strict: true,
|
|
10135
|
-
|
|
10130
|
+
noSearch: true,
|
|
10136
10131
|
options: [{
|
|
10137
10132
|
value: "live",
|
|
10138
10133
|
title: "webchat.settings.reasoning_display_live"
|
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-
|
|
14
|
+
load: () => import("./ChatWidget-uAyA3y0W.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": "
|
|
3
|
+
"version": "5.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": "^
|
|
31
|
-
"@coffer-org/server": "^
|
|
30
|
+
"@coffer-org/sdk": "^5.0.0",
|
|
31
|
+
"@coffer-org/server": "^5.0.0",
|
|
32
32
|
"lucide-react": "^1.17.0",
|
|
33
33
|
"react-markdown": "^10.1.0",
|
|
34
34
|
"remark-gfm": "^4.0.1"
|