@dowel-ui/react 0.3.0 → 0.4.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.
Files changed (71) hide show
  1. package/README.md +9 -8
  2. package/dist/components/ai-disclosure/ai-disclosure.d.ts +92 -0
  3. package/dist/components/ai-disclosure/ai-disclosure.d.ts.map +1 -0
  4. package/dist/components/ai-disclosure/ai-disclosure.js +122 -0
  5. package/dist/components/ai-disclosure/ai-disclosure.js.map +1 -0
  6. package/dist/components/ai-disclosure/index.d.ts +2 -0
  7. package/dist/components/ai-disclosure/index.js +2 -0
  8. package/dist/components/ai-disclosure/meta.js +17 -0
  9. package/dist/components/ai-disclosure/meta.js.map +1 -0
  10. package/dist/components/avatar/avatar.d.ts +1 -1
  11. package/dist/components/date-picker/date-picker.js +1 -1
  12. package/dist/components/diff-viewer/diff-model.d.ts +69 -0
  13. package/dist/components/diff-viewer/diff-model.d.ts.map +1 -0
  14. package/dist/components/diff-viewer/diff-model.js +180 -0
  15. package/dist/components/diff-viewer/diff-model.js.map +1 -0
  16. package/dist/components/diff-viewer/diff-viewer.d.ts +42 -0
  17. package/dist/components/diff-viewer/diff-viewer.d.ts.map +1 -0
  18. package/dist/components/diff-viewer/diff-viewer.js +212 -0
  19. package/dist/components/diff-viewer/diff-viewer.js.map +1 -0
  20. package/dist/components/diff-viewer/index.d.ts +3 -0
  21. package/dist/components/diff-viewer/index.js +3 -0
  22. package/dist/components/diff-viewer/meta.js +17 -0
  23. package/dist/components/diff-viewer/meta.js.map +1 -0
  24. package/dist/components/log-viewer/index.d.ts +3 -0
  25. package/dist/components/log-viewer/index.js +3 -0
  26. package/dist/components/log-viewer/log-stream.d.ts +68 -0
  27. package/dist/components/log-viewer/log-stream.d.ts.map +1 -0
  28. package/dist/components/log-viewer/log-stream.js +120 -0
  29. package/dist/components/log-viewer/log-stream.js.map +1 -0
  30. package/dist/components/log-viewer/log-viewer.d.ts +51 -0
  31. package/dist/components/log-viewer/log-viewer.d.ts.map +1 -0
  32. package/dist/components/log-viewer/log-viewer.js +284 -0
  33. package/dist/components/log-viewer/log-viewer.js.map +1 -0
  34. package/dist/components/log-viewer/meta.js +17 -0
  35. package/dist/components/log-viewer/meta.js.map +1 -0
  36. package/dist/components/spinner/spinner.d.ts +1 -1
  37. package/dist/components/time-range-picker/index.d.ts +3 -0
  38. package/dist/components/time-range-picker/index.js +3 -0
  39. package/dist/components/time-range-picker/meta.js +22 -0
  40. package/dist/components/time-range-picker/meta.js.map +1 -0
  41. package/dist/components/time-range-picker/time-expression.d.ts +88 -0
  42. package/dist/components/time-range-picker/time-expression.d.ts.map +1 -0
  43. package/dist/components/time-range-picker/time-expression.js +335 -0
  44. package/dist/components/time-range-picker/time-expression.js.map +1 -0
  45. package/dist/components/time-range-picker/time-range-picker.d.ts +65 -0
  46. package/dist/components/time-range-picker/time-range-picker.d.ts.map +1 -0
  47. package/dist/components/time-range-picker/time-range-picker.js +377 -0
  48. package/dist/components/time-range-picker/time-range-picker.js.map +1 -0
  49. package/dist/index.d.ts +12 -1
  50. package/dist/index.js +9 -2
  51. package/dist/registry/components.d.ts.map +1 -1
  52. package/dist/registry/components.js +58 -50
  53. package/dist/registry/components.js.map +1 -1
  54. package/package.json +6 -4
  55. package/src/components/ai-disclosure/ai-disclosure.tsx +244 -0
  56. package/src/components/ai-disclosure/index.ts +9 -0
  57. package/src/components/ai-disclosure/meta.ts +23 -0
  58. package/src/components/diff-viewer/diff-model.ts +256 -0
  59. package/src/components/diff-viewer/diff-viewer.tsx +339 -0
  60. package/src/components/diff-viewer/index.ts +17 -0
  61. package/src/components/diff-viewer/meta.ts +20 -0
  62. package/src/components/log-viewer/index.ts +21 -0
  63. package/src/components/log-viewer/log-stream.ts +176 -0
  64. package/src/components/log-viewer/log-viewer.tsx +424 -0
  65. package/src/components/log-viewer/meta.ts +21 -0
  66. package/src/components/time-range-picker/index.ts +25 -0
  67. package/src/components/time-range-picker/meta.ts +22 -0
  68. package/src/components/time-range-picker/time-expression.ts +491 -0
  69. package/src/components/time-range-picker/time-range-picker.tsx +522 -0
  70. package/src/index.ts +4 -0
  71. package/src/registry/components.ts +8 -0
package/README.md CHANGED
@@ -54,28 +54,29 @@ they want to change something.
54
54
 
55
55
  ## What is in it
56
56
 
57
- **56 components** and **8 blocks**, every one keyboard-operable and audited for
57
+ **60 components** and **8 blocks**, every one keyboard-operable and audited for
58
58
  contrast.
59
59
 
60
60
  **AI** — the reason this library exists
61
61
  Conversation · Message · Response · Prompt Input · Tool Call · Reasoning ·
62
- Sources · Model Selector · Token Usage · Agent Status · **Agent Plan** ·
63
- **Approval Request** · **Action Ledger** · **Structured Output** ·
64
- **Inline Completion**
62
+ Sources · Model Selector · Token Usage · Agent Status · Agent Plan ·
63
+ Approval Request · Action Ledger · Structured Output · Inline Completion ·
64
+ **Disclosure**
65
65
 
66
66
  **Forms**
67
67
  Input · Label · Checkbox · Radio Group · Switch · Slider · Select · Combobox ·
68
- Form · Calendar · Date Picker · **Tags Input** · **File Upload**
68
+ Form · Calendar · Date Picker · Tags Input · File Upload ·
69
+ **Time Range Picker**
69
70
 
70
71
  **Overlays**
71
72
  Dialog · Sheet · Drawer · Popover · Tooltip · Dropdown Menu
72
73
 
73
74
  **Data**
74
- Table · Data Table · Accordion · Activity Feed · Code Block · **Metric Delta** ·
75
- **Record Diff**
75
+ Table · Data Table · Accordion · Activity Feed · Code Block · Metric Delta ·
76
+ Record Diff · **Log Viewer** · **Diff Viewer**
76
77
 
77
78
  **Feedback**
78
- Alert · Toast · Progress · **Meter** · Skeleton · Spinner · Empty State
79
+ Alert · Toast · Progress · Meter · Skeleton · Spinner · Empty State
79
80
 
80
81
  **Navigation & layout**
81
82
  Tabs · Command · Pagination · Card · Badge · Avatar · Button · Separator
@@ -0,0 +1,92 @@
1
+ import { VariantProps } from "class-variance-authority";
2
+ import { ComponentPropsWithRef, ReactNode } from "react";
3
+ //#region src/components/ai-disclosure/ai-disclosure.d.ts
4
+ /**
5
+ * Telling someone they are looking at AI.
6
+ *
7
+ * Not one registry ships this — not AI Elements, assistant-ui, prompt-kit,
8
+ * CopilotKit or shadcn — while every one of them ships the chat surface that
9
+ * needs it. The gap is the reason this exists.
10
+ *
11
+ * The four `kind`s are not invented. They are the four human-visible situations
12
+ * the EU AI Act's Article 50 actually creates, which has applied since 2 August
13
+ * 2026: you are talking to a machine (50(1)); this media was generated or
14
+ * manipulated and resembles something real (50(4), "deep fakes"); this text was
15
+ * published to inform the public (50(4)); and the assisted case, which the same
16
+ * paragraph exempts where there is human review or editorial control — worth
17
+ * saying out loud rather than leaving to inference.
18
+ *
19
+ * What this cannot do, and what no React component can: Article 50(2) requires
20
+ * synthetic output to be marked *in a machine-readable format*, in the artifact
21
+ * itself, by whoever generated it. Rendering a badge does not do that, and a
22
+ * component that let you believe otherwise would be worse than none. Read the
23
+ * accessibility note and the docs page: this is a disclosure control, not a
24
+ * compliance product, and nothing here is legal advice.
25
+ */
26
+ type DisclosureKind = "interaction" | "generated" | "manipulated" | "assisted";
27
+ declare const disclosureVariants: (props?: ({
28
+ variant?: "banner" | "inline" | "overlay" | null | undefined;
29
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
30
+ interface AIDisclosureProps extends Omit<ComponentPropsWithRef<"div">, "children">, VariantProps<typeof disclosureVariants> {
31
+ kind: DisclosureKind;
32
+ /** Replaces the default wording. The default is a starting point, not a rule. */
33
+ label?: ReactNode;
34
+ /**
35
+ * Replaces the mark. The Commission publishes three icons for this and lets
36
+ * anyone use them without attribution — they are deliberately not bundled
37
+ * here, because an official mark shipped inside a component library ends up
38
+ * on content nobody checked. Bring your own, and the Commission's own words
39
+ * apply: using them "does not establish legal compliance by itself".
40
+ */
41
+ icon?: ReactNode;
42
+ /** A provenance panel, or anything else that belongs after the label. */
43
+ children?: ReactNode;
44
+ }
45
+ /**
46
+ * A disclosure, in words.
47
+ *
48
+ * The icon is decorative and the text is the disclosure. A sparkle alone is not
49
+ * a notice — it is a decoration that a sighted reader may or may not decode and
50
+ * that a screen reader passes over in silence, which is the failure mode the
51
+ * whole component exists to avoid.
52
+ */
53
+ declare function AIDisclosure({ className, kind, variant, label, icon, children, ...props }: AIDisclosureProps): import("react").JSX.Element;
54
+ interface ProvenanceClaim {
55
+ label: string;
56
+ value: ReactNode;
57
+ }
58
+ interface AIDisclosureProvenanceProps extends Omit<ComponentPropsWithRef<"button">, "children"> {
59
+ /**
60
+ * What is being claimed about this content, and by whom.
61
+ *
62
+ * Claims, not findings. They arrive as data — from a C2PA manifest your
63
+ * server read, from your own pipeline, from a vendor — and this renders them.
64
+ */
65
+ claims: ProvenanceClaim[];
66
+ /** Who asserts the claims. Named, because an unattributed claim is a rumour. */
67
+ source?: string;
68
+ /**
69
+ * Whether something you trust checked the claims, and said so.
70
+ *
71
+ * Supplied, never computed. Verifying a C2PA manifest means parsing signed
72
+ * COSE and walking a certificate chain; doing that in the browser would mean
73
+ * a wasm blob you cannot read, and "you own the source" would stop being
74
+ * true. It also cannot be trusted client-side even when it succeeds, because
75
+ * the page doing the checking is the page making the claim. Verify on a
76
+ * server and pass the answer down. Left undefined, this says so.
77
+ */
78
+ verified?: boolean;
79
+ triggerLabel?: string;
80
+ }
81
+ /**
82
+ * The metadata behind a disclosure, presented as claims rather than proof.
83
+ *
84
+ * The distinction is the entire point. "Made with Model X on 3 September" looks
85
+ * like a fact and is a string somebody put in a file. Rendering it beside a
86
+ * checkmark would launder an assertion into evidence, so the panel names who is
87
+ * asserting and states plainly whether anyone checked.
88
+ */
89
+ declare function AIDisclosureProvenance({ className, claims, source, verified, triggerLabel, ...props }: AIDisclosureProvenanceProps): import("react").JSX.Element;
90
+ //#endregion
91
+ export { AIDisclosure, AIDisclosureProps, AIDisclosureProvenance, AIDisclosureProvenanceProps, DisclosureKind, ProvenanceClaim, disclosureVariants };
92
+ //# sourceMappingURL=ai-disclosure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-disclosure.d.ts","names":[],"sources":["../../../src/components/ai-disclosure/ai-disclosure.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;KAgCY;cAgBN,qBAAkB;;6CAyBvB;UAegB,0BAEb,KAAK,2CACL,oBAAoB;EACtB,MAAM;;EAEN,QAAQ;;;;;;;;EAQR,OAAO;;EAEP,WAAW;;;;;;;;;;iBAWG,eACd,WACA,MACA,SACA,OACA,MACA,aACG,SACF,oCAAiB,IAAA;UAmBH;EACf;EACA,OAAO;;UAGQ,oCAAoC,KACnD;;;;;;;EASA,QAAQ;;EAER;;;;;;;;;;;EAWA;EACA;;;;;;;;;;iBAoBc,yBACd,WACA,QACA,QACA,UACA,iBACG,SACF,8CAA2B,IAAA"}
@@ -0,0 +1,122 @@
1
+ "use client";
2
+ import { focusRing, iconSlot } from "../../lib/styles.js";
3
+ import { cn } from "../../lib/utils.js";
4
+ import { Popover, PopoverContent, PopoverTrigger } from "../popover/popover.js";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { cva } from "class-variance-authority";
7
+ //#region src/components/ai-disclosure/ai-disclosure.tsx
8
+ /**
9
+ * What each kind says by default.
10
+ *
11
+ * Phrased for a reader rather than for a lawyer, and every one of them is
12
+ * overridable — the Act says the disclosure must be clear, not that it must use
13
+ * these words, and the right words depend on the product.
14
+ */
15
+ const DEFAULT_LABEL = {
16
+ interaction: "You are chatting with an AI assistant",
17
+ generated: "Generated by AI",
18
+ manipulated: "This media was altered by AI",
19
+ assisted: "AI-assisted, reviewed by a person"
20
+ };
21
+ const disclosureVariants = cva(cn("inline-flex w-fit items-center gap-1.5 font-medium", "[&_svg:not([class*='size-'])]:size-3.5", iconSlot), {
22
+ variants: { variant: {
23
+ /** Sits beside the thing it describes. */
24
+ inline: "rounded-full border border-border-strong px-2 py-0.5 text-xs",
25
+ /** Above a conversation, where Article 50(1) usually belongs. */
26
+ banner: "w-full rounded-lg border border-border bg-muted/40 px-3 py-2 text-xs text-muted-foreground",
27
+ /**
28
+ * Over the corner of an image or video. `--overlay` is a dark scrim in
29
+ * every shipped theme, so the text is white on purpose rather than
30
+ * following the theme — a label that inverts over a photograph is
31
+ * unreadable on half of them.
32
+ */
33
+ overlay: "rounded-md bg-overlay px-2 py-1 text-2xs text-white backdrop-blur-sm"
34
+ } },
35
+ defaultVariants: { variant: "inline" }
36
+ });
37
+ function SparkIcon() {
38
+ return /* @__PURE__ */ jsx("svg", {
39
+ viewBox: "0 0 24 24",
40
+ fill: "none",
41
+ "aria-hidden": "true",
42
+ children: /* @__PURE__ */ jsx("path", {
43
+ d: "M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9L12 3Z",
44
+ stroke: "currentColor",
45
+ strokeWidth: "2",
46
+ strokeLinejoin: "round"
47
+ })
48
+ });
49
+ }
50
+ /**
51
+ * A disclosure, in words.
52
+ *
53
+ * The icon is decorative and the text is the disclosure. A sparkle alone is not
54
+ * a notice — it is a decoration that a sighted reader may or may not decode and
55
+ * that a screen reader passes over in silence, which is the failure mode the
56
+ * whole component exists to avoid.
57
+ */
58
+ function AIDisclosure({ className, kind, variant, label, icon, children, ...props }) {
59
+ return /* @__PURE__ */ jsxs("div", {
60
+ "data-slot": "ai-disclosure",
61
+ "data-kind": kind,
62
+ className: cn(disclosureVariants({ variant }), className),
63
+ ...props,
64
+ children: [
65
+ icon ?? /* @__PURE__ */ jsx(SparkIcon, {}),
66
+ /* @__PURE__ */ jsx("span", { children: label ?? DEFAULT_LABEL[kind] }),
67
+ children
68
+ ]
69
+ });
70
+ }
71
+ function statusText(status, source) {
72
+ const by = source ? ` by ${source}` : "";
73
+ if (status === "verified") return `Checked${by} and the claims matched.`;
74
+ if (status === "failed") return `Checked${by} and the claims did not match.`;
75
+ return "Not checked. These are claims made about the content, not findings.";
76
+ }
77
+ /**
78
+ * The metadata behind a disclosure, presented as claims rather than proof.
79
+ *
80
+ * The distinction is the entire point. "Made with Model X on 3 September" looks
81
+ * like a fact and is a string somebody put in a file. Rendering it beside a
82
+ * checkmark would launder an assertion into evidence, so the panel names who is
83
+ * asserting and states plainly whether anyone checked.
84
+ */
85
+ function AIDisclosureProvenance({ className, claims, source, verified, triggerLabel = "Content details", ...props }) {
86
+ const status = verified === void 0 ? "unchecked" : verified ? "verified" : "failed";
87
+ return /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
88
+ asChild: true,
89
+ children: /* @__PURE__ */ jsx("button", {
90
+ type: "button",
91
+ "data-slot": "ai-disclosure-provenance-trigger",
92
+ className: cn("rounded-sm underline decoration-dotted underline-offset-2 hover:decoration-solid", focusRing, className),
93
+ ...props,
94
+ children: triggerLabel
95
+ })
96
+ }), /* @__PURE__ */ jsxs(PopoverContent, {
97
+ "aria-label": triggerLabel,
98
+ className: "w-80 text-xs",
99
+ children: [/* @__PURE__ */ jsx("dl", {
100
+ className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-1.5",
101
+ children: claims.map((claim) => /* @__PURE__ */ jsxs("div", {
102
+ className: "contents",
103
+ children: [/* @__PURE__ */ jsx("dt", {
104
+ className: "text-muted-foreground",
105
+ children: claim.label
106
+ }), /* @__PURE__ */ jsx("dd", {
107
+ className: "break-words",
108
+ children: claim.value
109
+ })]
110
+ }, claim.label))
111
+ }), /* @__PURE__ */ jsx("p", {
112
+ "data-slot": "ai-disclosure-provenance-status",
113
+ "data-status": status,
114
+ className: cn("mt-3 border-t border-border pt-2", status === "failed" ? "text-destructive" : "text-muted-foreground"),
115
+ children: statusText(status, source)
116
+ })]
117
+ })] });
118
+ }
119
+ //#endregion
120
+ export { AIDisclosure, AIDisclosureProvenance, disclosureVariants };
121
+
122
+ //# sourceMappingURL=ai-disclosure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-disclosure.js","names":[],"sources":["../../../src/components/ai-disclosure/ai-disclosure.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentPropsWithRef, ReactNode } from \"react\";\n\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/popover\";\nimport { focusRing, iconSlot } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Telling someone they are looking at AI.\n *\n * Not one registry ships this — not AI Elements, assistant-ui, prompt-kit,\n * CopilotKit or shadcn — while every one of them ships the chat surface that\n * needs it. The gap is the reason this exists.\n *\n * The four `kind`s are not invented. They are the four human-visible situations\n * the EU AI Act's Article 50 actually creates, which has applied since 2 August\n * 2026: you are talking to a machine (50(1)); this media was generated or\n * manipulated and resembles something real (50(4), \"deep fakes\"); this text was\n * published to inform the public (50(4)); and the assisted case, which the same\n * paragraph exempts where there is human review or editorial control — worth\n * saying out loud rather than leaving to inference.\n *\n * What this cannot do, and what no React component can: Article 50(2) requires\n * synthetic output to be marked *in a machine-readable format*, in the artifact\n * itself, by whoever generated it. Rendering a badge does not do that, and a\n * component that let you believe otherwise would be worse than none. Read the\n * accessibility note and the docs page: this is a disclosure control, not a\n * compliance product, and nothing here is legal advice.\n */\n\nexport type DisclosureKind = \"interaction\" | \"generated\" | \"manipulated\" | \"assisted\";\n\n/**\n * What each kind says by default.\n *\n * Phrased for a reader rather than for a lawyer, and every one of them is\n * overridable — the Act says the disclosure must be clear, not that it must use\n * these words, and the right words depend on the product.\n */\nconst DEFAULT_LABEL: Record<DisclosureKind, string> = {\n interaction: \"You are chatting with an AI assistant\",\n generated: \"Generated by AI\",\n manipulated: \"This media was altered by AI\",\n assisted: \"AI-assisted, reviewed by a person\",\n};\n\nconst disclosureVariants = cva(\n cn(\n \"inline-flex w-fit items-center gap-1.5 font-medium\",\n \"[&_svg:not([class*='size-'])]:size-3.5\",\n iconSlot,\n ),\n {\n variants: {\n variant: {\n /** Sits beside the thing it describes. */\n inline: \"rounded-full border border-border-strong px-2 py-0.5 text-xs\",\n /** Above a conversation, where Article 50(1) usually belongs. */\n banner:\n \"w-full rounded-lg border border-border bg-muted/40 px-3 py-2 text-xs text-muted-foreground\",\n /**\n * Over the corner of an image or video. `--overlay` is a dark scrim in\n * every shipped theme, so the text is white on purpose rather than\n * following the theme — a label that inverts over a photograph is\n * unreadable on half of them.\n */\n overlay: \"rounded-md bg-overlay px-2 py-1 text-2xs text-white backdrop-blur-sm\",\n },\n },\n defaultVariants: { variant: \"inline\" },\n },\n);\n\nfunction SparkIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <path\n d=\"M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9L12 3Z\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n\nexport interface AIDisclosureProps\n extends\n Omit<ComponentPropsWithRef<\"div\">, \"children\">,\n VariantProps<typeof disclosureVariants> {\n kind: DisclosureKind;\n /** Replaces the default wording. The default is a starting point, not a rule. */\n label?: ReactNode;\n /**\n * Replaces the mark. The Commission publishes three icons for this and lets\n * anyone use them without attribution — they are deliberately not bundled\n * here, because an official mark shipped inside a component library ends up\n * on content nobody checked. Bring your own, and the Commission's own words\n * apply: using them \"does not establish legal compliance by itself\".\n */\n icon?: ReactNode;\n /** A provenance panel, or anything else that belongs after the label. */\n children?: ReactNode;\n}\n\n/**\n * A disclosure, in words.\n *\n * The icon is decorative and the text is the disclosure. A sparkle alone is not\n * a notice — it is a decoration that a sighted reader may or may not decode and\n * that a screen reader passes over in silence, which is the failure mode the\n * whole component exists to avoid.\n */\nexport function AIDisclosure({\n className,\n kind,\n variant,\n label,\n icon,\n children,\n ...props\n}: AIDisclosureProps) {\n return (\n <div\n data-slot=\"ai-disclosure\"\n data-kind={kind}\n className={cn(disclosureVariants({ variant }), className)}\n {...props}\n >\n {icon ?? <SparkIcon />}\n <span>{label ?? DEFAULT_LABEL[kind]}</span>\n {children}\n </div>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Provenance */\n/* ------------------------------------------------------------------ */\n\nexport interface ProvenanceClaim {\n label: string;\n value: ReactNode;\n}\n\nexport interface AIDisclosureProvenanceProps extends Omit<\n ComponentPropsWithRef<\"button\">,\n \"children\"\n> {\n /**\n * What is being claimed about this content, and by whom.\n *\n * Claims, not findings. They arrive as data — from a C2PA manifest your\n * server read, from your own pipeline, from a vendor — and this renders them.\n */\n claims: ProvenanceClaim[];\n /** Who asserts the claims. Named, because an unattributed claim is a rumour. */\n source?: string;\n /**\n * Whether something you trust checked the claims, and said so.\n *\n * Supplied, never computed. Verifying a C2PA manifest means parsing signed\n * COSE and walking a certificate chain; doing that in the browser would mean\n * a wasm blob you cannot read, and \"you own the source\" would stop being\n * true. It also cannot be trusted client-side even when it succeeds, because\n * the page doing the checking is the page making the claim. Verify on a\n * server and pass the answer down. Left undefined, this says so.\n */\n verified?: boolean;\n triggerLabel?: string;\n}\n\ntype CheckStatus = \"verified\" | \"failed\" | \"unchecked\";\n\nfunction statusText(status: CheckStatus, source?: string): string {\n const by = source ? ` by ${source}` : \"\";\n if (status === \"verified\") return `Checked${by} and the claims matched.`;\n if (status === \"failed\") return `Checked${by} and the claims did not match.`;\n return \"Not checked. These are claims made about the content, not findings.\";\n}\n\n/**\n * The metadata behind a disclosure, presented as claims rather than proof.\n *\n * The distinction is the entire point. \"Made with Model X on 3 September\" looks\n * like a fact and is a string somebody put in a file. Rendering it beside a\n * checkmark would launder an assertion into evidence, so the panel names who is\n * asserting and states plainly whether anyone checked.\n */\nexport function AIDisclosureProvenance({\n className,\n claims,\n source,\n verified,\n triggerLabel = \"Content details\",\n ...props\n}: AIDisclosureProvenanceProps) {\n const status = verified === undefined ? \"unchecked\" : verified ? \"verified\" : \"failed\";\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"ai-disclosure-provenance-trigger\"\n className={cn(\n \"rounded-sm underline decoration-dotted underline-offset-2 hover:decoration-solid\",\n focusRing,\n className,\n )}\n {...props}\n >\n {triggerLabel}\n </button>\n </PopoverTrigger>\n <PopoverContent aria-label={triggerLabel} className=\"w-80 text-xs\">\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-3 gap-y-1.5\">\n {claims.map((claim) => (\n <div key={claim.label} className=\"contents\">\n <dt className=\"text-muted-foreground\">{claim.label}</dt>\n <dd className=\"break-words\">{claim.value}</dd>\n </div>\n ))}\n </dl>\n {/* Said in text, and last, so it qualifies everything above it rather\n than being a tick a reader has to interpret. */}\n <p\n data-slot=\"ai-disclosure-provenance-status\"\n data-status={status}\n className={cn(\n \"mt-3 border-t border-border pt-2\",\n status === \"failed\" ? \"text-destructive\" : \"text-muted-foreground\",\n )}\n >\n {statusText(status, source)}\n </p>\n </PopoverContent>\n </Popover>\n );\n}\n\nexport { disclosureVariants };\n"],"mappings":";;;;;;;;;;;;;;AAyCA,MAAM,gBAAgD;CACpD,aAAa;CACb,WAAW;CACX,aAAa;CACb,UAAU;AACZ;AAEA,MAAM,qBAAqB,IACzB,GACE,sDACA,0CACA,QACF,GACA;CACE,UAAU,EACR,SAAS;;EAEP,QAAQ;;EAER,QACE;;;;;;;EAOF,SAAS;CACX,EACF;CACA,iBAAiB,EAAE,SAAS,SAAS;AACvC,CACF;AAEA,SAAS,YAAY;CACnB,OACE,oBAAC,OAAD;EAAK,SAAQ;EAAY,MAAK;EAAO,eAAY;EAC/C,UAAA,oBAAC,QAAD;GACE,GAAE;GACF,QAAO;GACP,aAAY;GACZ,gBAAe;EAChB,CAAA;CACE,CAAA;AAET;;;;;;;;;AA6BA,SAAgB,aAAa,EAC3B,WACA,MACA,SACA,OACA,MACA,UACA,GAAG,SACiB;CACpB,OACE,qBAAC,OAAD;EACE,aAAU;EACV,aAAW;EACX,WAAW,GAAG,mBAAmB,EAAE,QAAQ,CAAC,GAAG,SAAS;EACxD,GAAI;EAJN,UAAA;GAMG,QAAQ,oBAAC,WAAD,CAAY,CAAA;GACrB,oBAAC,QAAD,EAAA,UAAO,SAAS,cAAc,MAAY,CAAA;GACzC;EACE;;AAET;AAwCA,SAAS,WAAW,QAAqB,QAAyB;CAChE,MAAM,KAAK,SAAS,OAAO,WAAW;CACtC,IAAI,WAAW,YAAY,OAAO,UAAU,GAAG;CAC/C,IAAI,WAAW,UAAU,OAAO,UAAU,GAAG;CAC7C,OAAO;AACT;;;;;;;;;AAUA,SAAgB,uBAAuB,EACrC,WACA,QACA,QACA,UACA,eAAe,mBACf,GAAG,SAC2B;CAC9B,MAAM,SAAS,aAAa,KAAA,IAAY,cAAc,WAAW,aAAa;CAE9E,OACE,qBAAC,SAAD,EAAA,UAAA,CACE,oBAAC,gBAAD;EAAgB,SAAA;EACd,UAAA,oBAAC,UAAD;GACE,MAAK;GACL,aAAU;GACV,WAAW,GACT,oFACA,WACA,SACF;GACA,GAAI;GAEH,UAAA;EACK,CAAA;CACM,CAAA,GAChB,qBAAC,gBAAD;EAAgB,cAAY;EAAc,WAAU;EAApD,UAAA,CACE,oBAAC,MAAD;GAAI,WAAU;GACX,UAAA,OAAO,KAAK,UACX,qBAAC,OAAD;IAAuB,WAAU;IAAjC,UAAA,CACE,oBAAC,MAAD;KAAI,WAAU;KAAyB,UAAA,MAAM;IAAU,CAAA,GACvD,oBAAC,MAAD;KAAI,WAAU;KAAe,UAAA,MAAM;IAAU,CAAA,CAC1C;GAHK,GAAA,MAAM,KAGX,CACN;EACC,CAAA,GAGJ,oBAAC,KAAD;GACE,aAAU;GACV,eAAa;GACb,WAAW,GACT,oCACA,WAAW,WAAW,qBAAqB,uBAC7C;GAEC,UAAA,WAAW,QAAQ,MAAM;EACzB,CAAA,CACW;CACT,CAAA,CAAA,EAAA,CAAA;AAEb"}
@@ -0,0 +1,2 @@
1
+ import { AIDisclosure, AIDisclosureProps, AIDisclosureProvenance, AIDisclosureProvenanceProps, DisclosureKind, ProvenanceClaim, disclosureVariants } from "./ai-disclosure.js";
2
+ export { AIDisclosure, type AIDisclosureProps, AIDisclosureProvenance, type AIDisclosureProvenanceProps, type DisclosureKind, type ProvenanceClaim, disclosureVariants };
@@ -0,0 +1,2 @@
1
+ import { AIDisclosure, AIDisclosureProvenance, disclosureVariants } from "./ai-disclosure.js";
2
+ export { AIDisclosure, AIDisclosureProvenance, disclosureVariants };
@@ -0,0 +1,17 @@
1
+ import { defineMeta } from "../../registry/schema.js";
2
+ //#region src/components/ai-disclosure/meta.ts
3
+ const meta = defineMeta({
4
+ name: "ai-disclosure",
5
+ title: "AI Disclosure",
6
+ description: "Tells a reader they are looking at AI, and what is actually known about it.",
7
+ category: "ai",
8
+ status: "stable",
9
+ dependencies: ["class-variance-authority"],
10
+ registryDependencies: ["popover"],
11
+ files: ["ai-disclosure.tsx"],
12
+ a11y: "The text is the disclosure and the icon is decoration, marked aria-hidden — a sparkle alone is a mark a sighted reader may not decode and a screen reader passes over in silence, which is the exact failure this component exists to prevent. Each of the four kinds carries wording of its own, so the distinction between generated, manipulated and human-reviewed survives for anyone who cannot see the styling. The provenance panel states in words whether anyone checked the claims and who, rather than implying it with a tick, and its three states are told apart by their sentences rather than by colour. Scope: this renders the human-visible disclosures of EU AI Act Article 50(1) and 50(4). It cannot satisfy 50(2), which requires machine-readable marking inside the artifact by whoever generated it, and no React component can. Not legal advice."
13
+ });
14
+ //#endregion
15
+ export { meta };
16
+
17
+ //# sourceMappingURL=meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta.js","names":[],"sources":["../../../src/components/ai-disclosure/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"ai-disclosure\",\n title: \"AI Disclosure\",\n description: \"Tells a reader they are looking at AI, and what is actually known about it.\",\n category: \"ai\",\n status: \"stable\",\n dependencies: [\"class-variance-authority\"],\n registryDependencies: [\"popover\"],\n files: [\"ai-disclosure.tsx\"],\n a11y:\n \"The text is the disclosure and the icon is decoration, marked aria-hidden — a sparkle alone \" +\n \"is a mark a sighted reader may not decode and a screen reader passes over in silence, which \" +\n \"is the exact failure this component exists to prevent. Each of the four kinds carries \" +\n \"wording of its own, so the distinction between generated, manipulated and human-reviewed \" +\n \"survives for anyone who cannot see the styling. The provenance panel states in words \" +\n \"whether anyone checked the claims and who, rather than implying it with a tick, and its \" +\n \"three states are told apart by their sentences rather than by colour. \" +\n \"Scope: this renders the human-visible disclosures of EU AI Act Article 50(1) and 50(4). \" +\n \"It cannot satisfy 50(2), which requires machine-readable marking inside the artifact by \" +\n \"whoever generated it, and no React component can. Not legal advice.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aAAa;CACb,UAAU;CACV,QAAQ;CACR,cAAc,CAAC,0BAA0B;CACzC,sBAAsB,CAAC,SAAS;CAChC,OAAO,CAAC,mBAAmB;CAC3B,MACE;AAUJ,CAAC"}
@@ -3,7 +3,7 @@ import { VariantProps } from "class-variance-authority";
3
3
  import { ComponentPropsWithRef } from "react";
4
4
  //#region src/components/avatar/avatar.d.ts
5
5
  declare const avatarVariants: (props?: ({
6
- size?: "sm" | "md" | "lg" | "xs" | "xl" | null | undefined;
6
+ size?: "sm" | "md" | "xs" | "lg" | "xl" | null | undefined;
7
7
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
8
8
  interface AvatarProps extends ComponentPropsWithRef<typeof Avatar.Root>, VariantProps<typeof avatarVariants> {}
9
9
  /** Image representation of a user or entity, with a text fallback. */
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { cn } from "../../lib/utils.js";
3
+ import { Popover, PopoverContent, PopoverTrigger } from "../popover/popover.js";
3
4
  import { Button } from "../button/button.js";
4
5
  import { Calendar } from "../calendar/calendar.js";
5
- import { Popover, PopoverContent, PopoverTrigger } from "../popover/popover.js";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  import { useState } from "react";
8
8
  //#region src/components/date-picker/date-picker.tsx
@@ -0,0 +1,69 @@
1
+ //#region src/components/diff-viewer/diff-model.d.ts
2
+ /**
3
+ * Turning two versions of a file into rows a diff can render.
4
+ *
5
+ * The algorithm is not the interesting part and is not reimplemented here —
6
+ * jsdiff does Myers properly, is BSD-licensed, has no dependencies of its own,
7
+ * and getting an O(ND) diff subtly wrong is a poor use of anybody's afternoon.
8
+ * What every packaged *viewer* welds on is a styling strategy: emotion in
9
+ * react-diff-viewer-continued, HTML strings and a stylesheet in diff2html. That
10
+ * is what cannot be reached by design tokens, and it is why this exists.
11
+ *
12
+ * Everything below is pure, so the grouping, the context collapsing and the
13
+ * word-level pairing can be tested without rendering anything.
14
+ */
15
+ type RowKind = "context" | "added" | "removed";
16
+ interface WordSegment {
17
+ text: string;
18
+ changed: boolean;
19
+ }
20
+ interface DiffRow {
21
+ kind: RowKind;
22
+ /** 1-based line number in the original. Absent on an added line. */
23
+ before?: number;
24
+ /** 1-based line number in the result. Absent on a removed line. */
25
+ after?: number;
26
+ content: string;
27
+ /**
28
+ * Word-level split, present only where a removed line pairs with an added
29
+ * one. Without a pair there is nothing to compare against, and highlighting
30
+ * the whole line as "changed" would be noise.
31
+ */
32
+ segments?: WordSegment[];
33
+ }
34
+ interface DiffHunk {
35
+ id: string;
36
+ rows: DiffRow[];
37
+ /** Context lines hidden before this hunk, if any. */
38
+ skippedBefore: number;
39
+ }
40
+ interface BuildDiffOptions {
41
+ /** Unchanged lines kept either side of a change. */
42
+ context?: number;
43
+ /**
44
+ * Compare word by word inside a changed line. Off for very large diffs,
45
+ * where the extra pass costs more than it explains.
46
+ */
47
+ words?: boolean;
48
+ }
49
+ declare function buildDiff(before: string, after: string, options?: BuildDiffOptions): DiffHunk[];
50
+ /**
51
+ * Groups rows into hunks, dropping context beyond `context` lines.
52
+ *
53
+ * A file with one changed line is otherwise thousands of rows of identical
54
+ * text, and the reader has to find the change in it.
55
+ */
56
+ declare function groupIntoHunks(rows: DiffRow[], context: number): DiffHunk[];
57
+ /** Row pairs for a side-by-side view, aligning removals against additions. */
58
+ declare function toSplitRows(rows: DiffRow[]): {
59
+ left: DiffRow | null;
60
+ right: DiffRow | null;
61
+ }[];
62
+ /** Counts, for a summary that says what the diff does before it is read. */
63
+ declare function countChanges(hunks: DiffHunk[]): {
64
+ added: number;
65
+ removed: number;
66
+ };
67
+ //#endregion
68
+ export { BuildDiffOptions, DiffHunk, DiffRow, RowKind, WordSegment, buildDiff, countChanges, groupIntoHunks, toSplitRows };
69
+ //# sourceMappingURL=diff-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-model.d.ts","names":[],"sources":["../../../src/components/diff-viewer/diff-model.ts"],"mappings":";;;;;;;;;;;;;;KAgBY;UAEK;EACf;EACA;;UAGe;EACf,MAAM;;EAEN;;EAEA;EACA;;;;;;EAMA,WAAW;;UAGI;EACf;EACA,MAAM;;EAEN;;UAGe;;EAEf;;;;;EAKA;;iBAoDc,UACd,gBACA,eACA,UAAS,mBACR;;;;;;;iBAiEa,eAAe,MAAM,WAAW,kBAAkB;;iBA+BlD,YACd,MAAM;EACH,MAAM;EAAgB,OAAO;;;iBAuClB,aAAa,OAAO;EAAe;EAAe"}
@@ -0,0 +1,180 @@
1
+ import { diffLines, diffWordsWithSpace } from "diff";
2
+ //#region src/components/diff-viewer/diff-model.ts
3
+ function splitLines(text) {
4
+ const lines = text.split("\n");
5
+ if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop();
6
+ return lines;
7
+ }
8
+ /**
9
+ * Gives both sides a trailing newline before they are compared.
10
+ *
11
+ * jsdiff's line tokens carry their own newline, so a final line written as `a`
12
+ * and one written as `a\n` are different tokens. Appending a line to a file
13
+ * that did not end in a newline would otherwise be reported as the last line
14
+ * being removed and re-added — a change the author did not make, sitting on
15
+ * top of the one they did.
16
+ */
17
+ function withTrailingNewline(text) {
18
+ return text.endsWith("\n") ? text : `${text}\n`;
19
+ }
20
+ /**
21
+ * Pairs removed lines with added ones inside a single change block.
22
+ *
23
+ * jsdiff reports a change as a run of removals followed by a run of additions.
24
+ * Comparing the first removal against the first addition is what turns "this
25
+ * line went away and another arrived" into "this word changed" — but only while
26
+ * the runs line up. Beyond the shorter run there is no counterpart, and
27
+ * inventing one produces confident nonsense.
28
+ */
29
+ function pairWords(removed, added) {
30
+ const pairs = Math.min(removed.length, added.length);
31
+ for (let index = 0; index < pairs; index += 1) {
32
+ const from = removed[index];
33
+ const to = added[index];
34
+ if (!from || !to) continue;
35
+ const parts = diffWordsWithSpace(from.content, to.content);
36
+ from.segments = parts.filter((part) => !part.added).map((part) => ({
37
+ text: part.value,
38
+ changed: Boolean(part.removed)
39
+ }));
40
+ to.segments = parts.filter((part) => !part.removed).map((part) => ({
41
+ text: part.value,
42
+ changed: Boolean(part.added)
43
+ }));
44
+ }
45
+ }
46
+ function buildDiff(before, after, options = {}) {
47
+ const { context = 3, words = true } = options;
48
+ const changes = diffLines(withTrailingNewline(before), withTrailingNewline(after));
49
+ const rows = [];
50
+ let beforeLine = 1;
51
+ let afterLine = 1;
52
+ let pendingRemoved = [];
53
+ let pendingAdded = [];
54
+ const flushPair = () => {
55
+ if (words && pendingRemoved.length > 0 && pendingAdded.length > 0) pairWords(pendingRemoved, pendingAdded);
56
+ pendingRemoved = [];
57
+ pendingAdded = [];
58
+ };
59
+ for (const change of changes) {
60
+ const lines = splitLines(change.value);
61
+ if (change.added) {
62
+ for (const content of lines) {
63
+ const row = {
64
+ kind: "added",
65
+ after: afterLine,
66
+ content
67
+ };
68
+ afterLine += 1;
69
+ rows.push(row);
70
+ pendingAdded.push(row);
71
+ }
72
+ continue;
73
+ }
74
+ if (change.removed) {
75
+ if (pendingAdded.length > 0) flushPair();
76
+ for (const content of lines) {
77
+ const row = {
78
+ kind: "removed",
79
+ before: beforeLine,
80
+ content
81
+ };
82
+ beforeLine += 1;
83
+ rows.push(row);
84
+ pendingRemoved.push(row);
85
+ }
86
+ continue;
87
+ }
88
+ flushPair();
89
+ for (const content of lines) {
90
+ rows.push({
91
+ kind: "context",
92
+ before: beforeLine,
93
+ after: afterLine,
94
+ content
95
+ });
96
+ beforeLine += 1;
97
+ afterLine += 1;
98
+ }
99
+ }
100
+ flushPair();
101
+ return groupIntoHunks(rows, context);
102
+ }
103
+ /**
104
+ * Groups rows into hunks, dropping context beyond `context` lines.
105
+ *
106
+ * A file with one changed line is otherwise thousands of rows of identical
107
+ * text, and the reader has to find the change in it.
108
+ */
109
+ function groupIntoHunks(rows, context) {
110
+ const changedIndexes = rows.map((row, index) => row.kind === "context" ? -1 : index).filter((index) => index >= 0);
111
+ if (changedIndexes.length === 0) return [];
112
+ const ranges = [];
113
+ for (const index of changedIndexes) {
114
+ const start = Math.max(0, index - context);
115
+ const end = Math.min(rows.length - 1, index + context);
116
+ const last = ranges[ranges.length - 1];
117
+ if (last && start <= last[1] + 1) last[1] = Math.max(last[1], end);
118
+ else ranges.push([start, end]);
119
+ }
120
+ let previousEnd = -1;
121
+ return ranges.map(([start, end], index) => {
122
+ const skippedBefore = start - previousEnd - 1;
123
+ previousEnd = end;
124
+ return {
125
+ id: `hunk-${String(index)}`,
126
+ rows: rows.slice(start, end + 1),
127
+ skippedBefore: Math.max(0, skippedBefore)
128
+ };
129
+ });
130
+ }
131
+ /** Row pairs for a side-by-side view, aligning removals against additions. */
132
+ function toSplitRows(rows) {
133
+ const pairs = [];
134
+ let index = 0;
135
+ while (index < rows.length) {
136
+ const row = rows[index];
137
+ if (!row) break;
138
+ if (row.kind === "context") {
139
+ pairs.push({
140
+ left: row,
141
+ right: row
142
+ });
143
+ index += 1;
144
+ continue;
145
+ }
146
+ const removed = [];
147
+ while (rows[index]?.kind === "removed") {
148
+ removed.push(rows[index]);
149
+ index += 1;
150
+ }
151
+ const added = [];
152
+ while (rows[index]?.kind === "added") {
153
+ added.push(rows[index]);
154
+ index += 1;
155
+ }
156
+ const height = Math.max(removed.length, added.length);
157
+ for (let offset = 0; offset < height; offset += 1) pairs.push({
158
+ left: removed[offset] ?? null,
159
+ right: added[offset] ?? null
160
+ });
161
+ }
162
+ return pairs;
163
+ }
164
+ /** Counts, for a summary that says what the diff does before it is read. */
165
+ function countChanges(hunks) {
166
+ let added = 0;
167
+ let removed = 0;
168
+ for (const hunk of hunks) for (const row of hunk.rows) {
169
+ if (row.kind === "added") added += 1;
170
+ if (row.kind === "removed") removed += 1;
171
+ }
172
+ return {
173
+ added,
174
+ removed
175
+ };
176
+ }
177
+ //#endregion
178
+ export { buildDiff, countChanges, groupIntoHunks, toSplitRows };
179
+
180
+ //# sourceMappingURL=diff-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-model.js","names":[],"sources":["../../../src/components/diff-viewer/diff-model.ts"],"sourcesContent":["import { diffLines, diffWordsWithSpace } from \"diff\";\n\n/**\n * Turning two versions of a file into rows a diff can render.\n *\n * The algorithm is not the interesting part and is not reimplemented here —\n * jsdiff does Myers properly, is BSD-licensed, has no dependencies of its own,\n * and getting an O(ND) diff subtly wrong is a poor use of anybody's afternoon.\n * What every packaged *viewer* welds on is a styling strategy: emotion in\n * react-diff-viewer-continued, HTML strings and a stylesheet in diff2html. That\n * is what cannot be reached by design tokens, and it is why this exists.\n *\n * Everything below is pure, so the grouping, the context collapsing and the\n * word-level pairing can be tested without rendering anything.\n */\n\nexport type RowKind = \"context\" | \"added\" | \"removed\";\n\nexport interface WordSegment {\n text: string;\n changed: boolean;\n}\n\nexport interface DiffRow {\n kind: RowKind;\n /** 1-based line number in the original. Absent on an added line. */\n before?: number;\n /** 1-based line number in the result. Absent on a removed line. */\n after?: number;\n content: string;\n /**\n * Word-level split, present only where a removed line pairs with an added\n * one. Without a pair there is nothing to compare against, and highlighting\n * the whole line as \"changed\" would be noise.\n */\n segments?: WordSegment[];\n}\n\nexport interface DiffHunk {\n id: string;\n rows: DiffRow[];\n /** Context lines hidden before this hunk, if any. */\n skippedBefore: number;\n}\n\nexport interface BuildDiffOptions {\n /** Unchanged lines kept either side of a change. */\n context?: number;\n /**\n * Compare word by word inside a changed line. Off for very large diffs,\n * where the extra pass costs more than it explains.\n */\n words?: boolean;\n}\n\nfunction splitLines(text: string): string[] {\n const lines = text.split(\"\\n\");\n // A trailing newline produces a final empty element that is not a line.\n if (lines.length > 1 && lines[lines.length - 1] === \"\") lines.pop();\n return lines;\n}\n\n/**\n * Gives both sides a trailing newline before they are compared.\n *\n * jsdiff's line tokens carry their own newline, so a final line written as `a`\n * and one written as `a\\n` are different tokens. Appending a line to a file\n * that did not end in a newline would otherwise be reported as the last line\n * being removed and re-added — a change the author did not make, sitting on\n * top of the one they did.\n */\nfunction withTrailingNewline(text: string): string {\n return text.endsWith(\"\\n\") ? text : `${text}\\n`;\n}\n\n/**\n * Pairs removed lines with added ones inside a single change block.\n *\n * jsdiff reports a change as a run of removals followed by a run of additions.\n * Comparing the first removal against the first addition is what turns \"this\n * line went away and another arrived\" into \"this word changed\" — but only while\n * the runs line up. Beyond the shorter run there is no counterpart, and\n * inventing one produces confident nonsense.\n */\nfunction pairWords(removed: DiffRow[], added: DiffRow[]): void {\n const pairs = Math.min(removed.length, added.length);\n\n for (let index = 0; index < pairs; index += 1) {\n const from = removed[index];\n const to = added[index];\n if (!from || !to) continue;\n\n const parts = diffWordsWithSpace(from.content, to.content);\n\n from.segments = parts\n .filter((part) => !part.added)\n .map((part) => ({ text: part.value, changed: Boolean(part.removed) }));\n\n to.segments = parts\n .filter((part) => !part.removed)\n .map((part) => ({ text: part.value, changed: Boolean(part.added) }));\n }\n}\n\nexport function buildDiff(\n before: string,\n after: string,\n options: BuildDiffOptions = {},\n): DiffHunk[] {\n const { context = 3, words = true } = options;\n\n const changes = diffLines(withTrailingNewline(before), withTrailingNewline(after));\n const rows: DiffRow[] = [];\n let beforeLine = 1;\n let afterLine = 1;\n\n // Flat rows first. Grouping into hunks is a separate concern and mixing the\n // two is how these implementations become unreadable.\n let pendingRemoved: DiffRow[] = [];\n let pendingAdded: DiffRow[] = [];\n\n const flushPair = () => {\n if (words && pendingRemoved.length > 0 && pendingAdded.length > 0) {\n pairWords(pendingRemoved, pendingAdded);\n }\n pendingRemoved = [];\n pendingAdded = [];\n };\n\n for (const change of changes) {\n const lines = splitLines(change.value);\n\n if (change.added) {\n for (const content of lines) {\n const row: DiffRow = { kind: \"added\", after: afterLine, content };\n afterLine += 1;\n rows.push(row);\n pendingAdded.push(row);\n }\n continue;\n }\n\n if (change.removed) {\n // A removal run that follows an addition run starts a new pairing.\n if (pendingAdded.length > 0) flushPair();\n for (const content of lines) {\n const row: DiffRow = { kind: \"removed\", before: beforeLine, content };\n beforeLine += 1;\n rows.push(row);\n pendingRemoved.push(row);\n }\n continue;\n }\n\n flushPair();\n for (const content of lines) {\n rows.push({ kind: \"context\", before: beforeLine, after: afterLine, content });\n beforeLine += 1;\n afterLine += 1;\n }\n }\n\n flushPair();\n\n return groupIntoHunks(rows, context);\n}\n\n/**\n * Groups rows into hunks, dropping context beyond `context` lines.\n *\n * A file with one changed line is otherwise thousands of rows of identical\n * text, and the reader has to find the change in it.\n */\nexport function groupIntoHunks(rows: DiffRow[], context: number): DiffHunk[] {\n const changedIndexes = rows\n .map((row, index) => (row.kind === \"context\" ? -1 : index))\n .filter((index) => index >= 0);\n\n if (changedIndexes.length === 0) return [];\n\n // Ranges of rows to keep, then merged where their context overlaps —\n // otherwise two nearby changes produce two hunks separated by nothing.\n const ranges: [number, number][] = [];\n for (const index of changedIndexes) {\n const start = Math.max(0, index - context);\n const end = Math.min(rows.length - 1, index + context);\n const last = ranges[ranges.length - 1];\n if (last && start <= last[1] + 1) last[1] = Math.max(last[1], end);\n else ranges.push([start, end]);\n }\n\n let previousEnd = -1;\n return ranges.map(([start, end], index) => {\n const skippedBefore = start - previousEnd - 1;\n previousEnd = end;\n return {\n id: `hunk-${String(index)}`,\n rows: rows.slice(start, end + 1),\n skippedBefore: Math.max(0, skippedBefore),\n };\n });\n}\n\n/** Row pairs for a side-by-side view, aligning removals against additions. */\nexport function toSplitRows(\n rows: DiffRow[],\n): { left: DiffRow | null; right: DiffRow | null }[] {\n const pairs: { left: DiffRow | null; right: DiffRow | null }[] = [];\n let index = 0;\n\n while (index < rows.length) {\n const row = rows[index];\n if (!row) break;\n\n if (row.kind === \"context\") {\n pairs.push({ left: row, right: row });\n index += 1;\n continue;\n }\n\n // Take the whole removal run and the whole addition run that follows, then\n // lay them alongside each other. Emitting them in document order instead\n // would put every removal above every addition, which is the unified view\n // wearing a two-column costume.\n const removed: DiffRow[] = [];\n while (rows[index]?.kind === \"removed\") {\n removed.push(rows[index] as DiffRow);\n index += 1;\n }\n const added: DiffRow[] = [];\n while (rows[index]?.kind === \"added\") {\n added.push(rows[index] as DiffRow);\n index += 1;\n }\n\n const height = Math.max(removed.length, added.length);\n for (let offset = 0; offset < height; offset += 1) {\n pairs.push({ left: removed[offset] ?? null, right: added[offset] ?? null });\n }\n }\n\n return pairs;\n}\n\n/** Counts, for a summary that says what the diff does before it is read. */\nexport function countChanges(hunks: DiffHunk[]): { added: number; removed: number } {\n let added = 0;\n let removed = 0;\n for (const hunk of hunks) {\n for (const row of hunk.rows) {\n if (row.kind === \"added\") added += 1;\n if (row.kind === \"removed\") removed += 1;\n }\n }\n return { added, removed };\n}\n"],"mappings":"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"}