camox 0.54.0 → 0.56.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/features/canvas/CamoxCanvas.js +14 -11
- package/dist/features/canvas/CanvasLoadingScreen.js +1 -1
- package/dist/features/canvas/sidebar/CanvasViewHeader.js +1 -1
- package/dist/features/canvas/sidebar/{CanvasSidebar.js → ContentSidebar.js} +5 -5
- package/dist/features/canvas/sidebar/{CanvasSidebarFrame.js → ContentSidebarFrame.js} +4 -4
- package/dist/features/canvas/sidebar/{CanvasSidebarSkeleton.js → ContentSidebarSkeleton.js} +7 -7
- package/dist/features/preview/CamoxPreview.js +2 -2
- package/dist/features/preview/components/PreviewToolbar.js +1 -1
- package/dist/features/preview/components/SelectionEditor.js +2 -2
- package/dist/features/preview/components/SidebarStack.js +11 -13
- package/dist/features/preview/components/StudioEditingFallback.js +3 -3
- package/dist/features/routes/pageRuntime.d.ts +1 -1
- package/dist/features/runtime/RuntimeChrome.js +4 -4
- package/dist/features/studio/components/AgentSidebar.js +56 -0
- package/dist/features/studio/components/{ChatSidebar.js → ChatWidget.js} +60 -169
- package/dist/features/studio/components/DraftsControl.js +17 -17
- package/dist/features/studio/components/ReviewDraftSheet.js +38 -38
- package/dist/features/studio/components/Squareling.js +10 -8
- package/dist/features/studio/components/ThreadMessages.js +128 -105
- package/dist/features/studio/components/WorkersWidget.js +52 -0
- package/dist/features/studio/studioStore.js +5 -5
- package/dist/lib/queries.js +1 -1
- package/dist/package.js +1 -1
- package/dist/studio.css +1 -1
- package/package.json +4 -4
- package/skills/camox/SKILL.md +3 -10
- package/skills/camox/references/artifacts.md +2 -2
- package/skills/camox/references/block-definitions.md +1 -1
- package/skills/camox/references/layout-definitions.md +1 -1
- package/skills/camox/references/routed-layouts.md +2 -2
- package/skills/camox-cli/SKILL.md +15 -0
- package/skills/{camox → camox-cli}/references/cli-common.md +1 -1
- package/skills/{camox → camox-cli}/references/cli-content.md +2 -2
- package/skills/{camox → camox-cli}/references/cli-publishing.md +4 -4
- package/skills/camox-expand/SKILL.md +1 -1
- package/skills/camoxify/SKILL.md +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Squareling } from "./Squareling.js";
|
|
2
2
|
import { ThreadMarkdown } from "./ThreadMarkdown.js";
|
|
3
3
|
import { c } from "react/compiler-runtime";
|
|
4
|
+
import * as React from "react";
|
|
4
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
import { Avatar, AvatarFallback, AvatarImage } from "@camox/ui/avatar";
|
|
6
7
|
import { Bubble, BubbleContent } from "@camox/ui/bubble";
|
|
@@ -9,38 +10,42 @@ import { MessageScroller, MessageScrollerButton, MessageScrollerContent, Message
|
|
|
9
10
|
|
|
10
11
|
//#region src/features/studio/components/ThreadMessages.tsx
|
|
11
12
|
/**
|
|
12
|
-
* A thread's messages, and the agent's squareling
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* A thread's messages, and the agent's squareling, which always lives in it. An empty thread has
|
|
14
|
+
* it greet the editor; when the first message arrives, it moves to the end of the thread, and
|
|
15
|
+
* stays there after each message. While a run is going or a message is still `sending` (on its
|
|
16
|
+
* way to the thread, which hasn't accepted it yet), its face says whether the agent thinks or
|
|
17
|
+
* acts (uses its tools, or builds its code). While the end of a turn builds the agent's code, or
|
|
18
|
+
* the agent fixes a build error, it says so in words too. Each editor's message shows its
|
|
19
|
+
* sender's avatar, without their name.
|
|
18
20
|
*/
|
|
19
21
|
function ThreadMessages(t0) {
|
|
20
|
-
const $ = c(
|
|
21
|
-
const { view, sending: t1
|
|
22
|
+
const $ = c(19);
|
|
23
|
+
const { view, sending: t1 } = t0;
|
|
22
24
|
const sending = t1 === void 0 ? false : t1;
|
|
23
25
|
const working = view.running || sending;
|
|
24
|
-
|
|
26
|
+
const empty = view.messages.length === 0 && !working;
|
|
27
|
+
const squareling = useSquarelingMorph(empty);
|
|
28
|
+
if (empty) {
|
|
25
29
|
let t2;
|
|
26
|
-
if ($[0] !==
|
|
27
|
-
t2 =
|
|
28
|
-
|
|
30
|
+
if ($[0] !== squareling) {
|
|
31
|
+
t2 = /* @__PURE__ */ jsx(Squareling, {
|
|
32
|
+
ref: squareling,
|
|
33
|
+
status: "idle",
|
|
34
|
+
className: "size-10"
|
|
35
|
+
});
|
|
36
|
+
$[0] = squareling;
|
|
29
37
|
$[1] = t2;
|
|
30
38
|
} else t2 = $[1];
|
|
31
39
|
let t3;
|
|
32
40
|
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
|
33
|
-
t3 = /* @__PURE__ */ jsx("p", {
|
|
34
|
-
"data-thread-empty": true,
|
|
35
|
-
className: "text-muted-foreground flex min-h-0 flex-1 items-center justify-center p-4 text-center text-sm",
|
|
36
|
-
children: "Ask Camox about your website’s content."
|
|
37
|
-
});
|
|
41
|
+
t3 = /* @__PURE__ */ jsx("p", { children: "How can I help?" });
|
|
38
42
|
$[2] = t3;
|
|
39
43
|
} else t3 = $[2];
|
|
40
44
|
let t4;
|
|
41
45
|
if ($[3] !== t2) {
|
|
42
46
|
t4 = /* @__PURE__ */ jsxs("div", {
|
|
43
|
-
|
|
47
|
+
"data-thread-empty": true,
|
|
48
|
+
className: "text-muted-foreground flex min-h-0 flex-1 flex-col items-center justify-center gap-3 p-4 text-center text-sm",
|
|
44
49
|
children: [t2, t3]
|
|
45
50
|
});
|
|
46
51
|
$[3] = t2;
|
|
@@ -49,108 +54,151 @@ function ThreadMessages(t0) {
|
|
|
49
54
|
return t4;
|
|
50
55
|
}
|
|
51
56
|
let t2;
|
|
52
|
-
if ($[5] !==
|
|
53
|
-
t2 =
|
|
54
|
-
$[5] = continuedIn;
|
|
55
|
-
$[6] = t2;
|
|
56
|
-
} else t2 = $[6];
|
|
57
|
-
let t3;
|
|
58
|
-
if ($[7] !== view || $[8] !== working) {
|
|
59
|
-
t3 = view.messages.map((message, index) => /* @__PURE__ */ jsxs(MessageScrollerItem, {
|
|
57
|
+
if ($[5] !== squareling || $[6] !== view || $[7] !== working) {
|
|
58
|
+
t2 = view.messages.map((message, index) => /* @__PURE__ */ jsxs(MessageScrollerItem, {
|
|
60
59
|
messageId: message.id,
|
|
61
60
|
scrollAnchor: message.from === "editor",
|
|
62
61
|
className: "flex flex-col gap-4",
|
|
63
|
-
children: [/* @__PURE__ */ jsx(ThreadMessageRow, { message }),
|
|
62
|
+
children: [/* @__PURE__ */ jsx(ThreadMessageRow, { message }), index === view.messages.length - 1 && /* @__PURE__ */ jsx(AgentPresence, {
|
|
63
|
+
view,
|
|
64
|
+
working,
|
|
65
|
+
squareling
|
|
66
|
+
})]
|
|
64
67
|
}, message.id));
|
|
65
|
-
$[
|
|
66
|
-
$[
|
|
67
|
-
$[
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
$[5] = squareling;
|
|
69
|
+
$[6] = view;
|
|
70
|
+
$[7] = working;
|
|
71
|
+
$[8] = t2;
|
|
72
|
+
} else t2 = $[8];
|
|
73
|
+
let t3;
|
|
74
|
+
if ($[9] !== squareling || $[10] !== view || $[11] !== working) {
|
|
75
|
+
t3 = view.messages.length === 0 && /* @__PURE__ */ jsx(AgentPresence, {
|
|
76
|
+
view,
|
|
77
|
+
working,
|
|
78
|
+
squareling
|
|
79
|
+
});
|
|
80
|
+
$[9] = squareling;
|
|
72
81
|
$[10] = view;
|
|
73
82
|
$[11] = working;
|
|
74
|
-
$[12] =
|
|
75
|
-
} else
|
|
76
|
-
let
|
|
77
|
-
if ($[13] !== t2 || $[14] !== t3
|
|
78
|
-
|
|
83
|
+
$[12] = t3;
|
|
84
|
+
} else t3 = $[12];
|
|
85
|
+
let t4;
|
|
86
|
+
if ($[13] !== t2 || $[14] !== t3) {
|
|
87
|
+
t4 = /* @__PURE__ */ jsx(MessageScrollerViewport, { children: /* @__PURE__ */ jsxs(MessageScrollerContent, {
|
|
79
88
|
className: "flex flex-col gap-4 px-2 py-3",
|
|
80
|
-
children: [
|
|
81
|
-
t2,
|
|
82
|
-
t3,
|
|
83
|
-
t4
|
|
84
|
-
]
|
|
89
|
+
children: [t2, t3]
|
|
85
90
|
}) });
|
|
86
91
|
$[13] = t2;
|
|
87
92
|
$[14] = t3;
|
|
88
93
|
$[15] = t4;
|
|
94
|
+
} else t4 = $[15];
|
|
95
|
+
let t5;
|
|
96
|
+
if ($[16] === Symbol.for("react.memo_cache_sentinel")) {
|
|
97
|
+
t5 = /* @__PURE__ */ jsx(MessageScrollerButton, {});
|
|
89
98
|
$[16] = t5;
|
|
90
99
|
} else t5 = $[16];
|
|
91
100
|
let t6;
|
|
92
|
-
if ($[17]
|
|
93
|
-
t6 = /* @__PURE__ */ jsx(
|
|
94
|
-
$[17] = t6;
|
|
95
|
-
} else t6 = $[17];
|
|
96
|
-
let t7;
|
|
97
|
-
if ($[18] !== t5) {
|
|
98
|
-
t7 = /* @__PURE__ */ jsx(MessageScrollerProvider, { children: /* @__PURE__ */ jsxs(MessageScroller, {
|
|
101
|
+
if ($[17] !== t4) {
|
|
102
|
+
t6 = /* @__PURE__ */ jsx(MessageScrollerProvider, { children: /* @__PURE__ */ jsxs(MessageScroller, {
|
|
99
103
|
className: "min-h-0 flex-1",
|
|
100
|
-
children: [
|
|
104
|
+
children: [t4, t5]
|
|
101
105
|
}) });
|
|
102
|
-
$[
|
|
103
|
-
$[
|
|
104
|
-
} else
|
|
105
|
-
return
|
|
106
|
+
$[17] = t4;
|
|
107
|
+
$[18] = t6;
|
|
108
|
+
} else t6 = $[18];
|
|
109
|
+
return t6;
|
|
106
110
|
}
|
|
111
|
+
/** How long the greeting's squareling takes to reach the end of the thread, in ms. */
|
|
112
|
+
const MORPH_DURATION = 350;
|
|
113
|
+
/** A strong ease-in-out, for moving across the screen. */
|
|
114
|
+
const MORPH_EASING = "cubic-bezier(0.77, 0, 0.175, 1)";
|
|
107
115
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
116
|
+
* Moves the squareling from where the greeting showed it to the end of the thread, as the first
|
|
117
|
+
* message arrives, so it reads as the same one. Without motion for editors who reduce it.
|
|
110
118
|
*/
|
|
111
|
-
function
|
|
112
|
-
const $ = c(
|
|
113
|
-
const
|
|
119
|
+
function useSquarelingMorph(empty) {
|
|
120
|
+
const $ = c(2);
|
|
121
|
+
const node = React.useRef(null);
|
|
122
|
+
const greeting = React.useRef(null);
|
|
123
|
+
let t0;
|
|
124
|
+
if ($[0] !== empty) {
|
|
125
|
+
t0 = () => {
|
|
126
|
+
const element = node.current;
|
|
127
|
+
if (!element) return;
|
|
128
|
+
if (empty) {
|
|
129
|
+
greeting.current = element.getBoundingClientRect();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const from = greeting.current;
|
|
133
|
+
greeting.current = null;
|
|
134
|
+
if (!from || matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
135
|
+
const to = element.getBoundingClientRect();
|
|
136
|
+
if (to.width === 0) return;
|
|
137
|
+
const dx = from.left + from.width / 2 - (to.left + to.width / 2);
|
|
138
|
+
const dy = from.top + from.height / 2 - (to.top + to.height / 2);
|
|
139
|
+
element.animate([{ transform: `translate(${dx}px, ${dy}px) scale(${from.width / to.width})` }, { transform: "none" }], {
|
|
140
|
+
duration: MORPH_DURATION,
|
|
141
|
+
easing: MORPH_EASING
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
$[0] = empty;
|
|
145
|
+
$[1] = t0;
|
|
146
|
+
} else t0 = $[1];
|
|
147
|
+
React.useLayoutEffect(t0);
|
|
148
|
+
return node;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The squareling at the end of the thread: idle between turns, at work during one. Shown in the
|
|
152
|
+
* last message, not after it: the scroller only brings a message the editor just sent into view
|
|
153
|
+
* when it's appended last, and it arrives while the agent is already working.
|
|
154
|
+
*/
|
|
155
|
+
function AgentPresence(t0) {
|
|
156
|
+
const $ = c(13);
|
|
157
|
+
const { view: t1, working, squareling } = t0;
|
|
114
158
|
const { status, usingTools } = t1;
|
|
115
159
|
let t2;
|
|
116
|
-
if ($[0] !== status || $[1] !== usingTools) {
|
|
117
|
-
t2 = squarelingStatus(status, usingTools);
|
|
160
|
+
if ($[0] !== status || $[1] !== usingTools || $[2] !== working) {
|
|
161
|
+
t2 = working ? squarelingStatus(status, usingTools) : "idle";
|
|
118
162
|
$[0] = status;
|
|
119
163
|
$[1] = usingTools;
|
|
120
|
-
$[2] =
|
|
121
|
-
|
|
164
|
+
$[2] = working;
|
|
165
|
+
$[3] = t2;
|
|
166
|
+
} else t2 = $[3];
|
|
122
167
|
let t3;
|
|
123
|
-
if ($[
|
|
168
|
+
if ($[4] !== squareling || $[5] !== t2) {
|
|
124
169
|
t3 = /* @__PURE__ */ jsx(Squareling, {
|
|
170
|
+
ref: squareling,
|
|
125
171
|
status: t2,
|
|
126
172
|
className: "size-6"
|
|
127
173
|
});
|
|
128
|
-
$[
|
|
129
|
-
$[
|
|
130
|
-
|
|
174
|
+
$[4] = squareling;
|
|
175
|
+
$[5] = t2;
|
|
176
|
+
$[6] = t3;
|
|
177
|
+
} else t3 = $[6];
|
|
131
178
|
let t4;
|
|
132
|
-
if ($[
|
|
133
|
-
t4 = status ? /* @__PURE__ */ jsx("span", {
|
|
179
|
+
if ($[7] !== status || $[8] !== working) {
|
|
180
|
+
t4 = working && (status ? /* @__PURE__ */ jsx("span", {
|
|
134
181
|
className: "shimmer",
|
|
135
182
|
children: STATUS_LINES[status]
|
|
136
183
|
}) : /* @__PURE__ */ jsx("span", {
|
|
137
184
|
className: "sr-only",
|
|
138
185
|
children: "Working…"
|
|
139
|
-
});
|
|
140
|
-
$[
|
|
141
|
-
$[
|
|
142
|
-
|
|
186
|
+
}));
|
|
187
|
+
$[7] = status;
|
|
188
|
+
$[8] = working;
|
|
189
|
+
$[9] = t4;
|
|
190
|
+
} else t4 = $[9];
|
|
143
191
|
let t5;
|
|
144
|
-
if ($[
|
|
192
|
+
if ($[10] !== t3 || $[11] !== t4) {
|
|
145
193
|
t5 = /* @__PURE__ */ jsxs("div", {
|
|
146
194
|
role: "status",
|
|
147
195
|
className: "text-muted-foreground flex items-center gap-2 text-sm",
|
|
148
196
|
children: [t3, t4]
|
|
149
197
|
});
|
|
150
|
-
$[
|
|
151
|
-
$[
|
|
152
|
-
$[
|
|
153
|
-
} else t5 = $[
|
|
198
|
+
$[10] = t3;
|
|
199
|
+
$[11] = t4;
|
|
200
|
+
$[12] = t5;
|
|
201
|
+
} else t5 = $[12];
|
|
154
202
|
return t5;
|
|
155
203
|
}
|
|
156
204
|
function squarelingStatus(status, usingTools) {
|
|
@@ -242,31 +290,6 @@ function ThreadMessageRow(t0) {
|
|
|
242
290
|
} else t3 = $[13];
|
|
243
291
|
return t3;
|
|
244
292
|
}
|
|
245
|
-
function ContinuedIn(t0) {
|
|
246
|
-
const $ = c(4);
|
|
247
|
-
const { link } = t0;
|
|
248
|
-
let t1;
|
|
249
|
-
if ($[0] !== link.href || $[1] !== link.label || $[2] !== link.onOpen) {
|
|
250
|
-
t1 = /* @__PURE__ */ jsxs("p", {
|
|
251
|
-
className: "text-muted-foreground px-2 pt-3 text-center text-sm",
|
|
252
|
-
children: [
|
|
253
|
-
"Continued in",
|
|
254
|
-
" ",
|
|
255
|
-
/* @__PURE__ */ jsx("a", {
|
|
256
|
-
href: link.href,
|
|
257
|
-
onClick: link.onOpen,
|
|
258
|
-
className: "text-foreground underline",
|
|
259
|
-
children: link.label
|
|
260
|
-
})
|
|
261
|
-
]
|
|
262
|
-
});
|
|
263
|
-
$[0] = link.href;
|
|
264
|
-
$[1] = link.label;
|
|
265
|
-
$[2] = link.onOpen;
|
|
266
|
-
$[3] = t1;
|
|
267
|
-
} else t1 = $[3];
|
|
268
|
-
return t1;
|
|
269
|
-
}
|
|
270
293
|
const initials = (name) => name.split(" ").map((part) => part[0]).join("").toUpperCase().slice(0, 2);
|
|
271
294
|
function SenderAvatar(t0) {
|
|
272
295
|
const $ = c(10);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { cn } from "../../../lib/utils.js";
|
|
2
|
+
import { BOTTOM_CARD_EDGES, CARD_BODY_EDGES } from "../../preview/components/SidebarStack.js";
|
|
3
|
+
import { c } from "react/compiler-runtime";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
|
|
6
|
+
//#region src/features/studio/components/WorkersWidget.tsx
|
|
7
|
+
/** The Agent Sidebar's Workers widget, over the Chat widget. A placeholder for now. */
|
|
8
|
+
function WorkersWidget(t0) {
|
|
9
|
+
const $ = c(6);
|
|
10
|
+
const { className } = t0;
|
|
11
|
+
let t1;
|
|
12
|
+
if ($[0] !== className) {
|
|
13
|
+
t1 = cn("flex flex-col", className);
|
|
14
|
+
$[0] = className;
|
|
15
|
+
$[1] = t1;
|
|
16
|
+
} else t1 = $[1];
|
|
17
|
+
let t2;
|
|
18
|
+
let t3;
|
|
19
|
+
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
|
20
|
+
t2 = /* @__PURE__ */ jsx("header", {
|
|
21
|
+
className: cn("bg-background flex h-11 min-w-0 shrink-0 items-center justify-center px-3", BOTTOM_CARD_EDGES),
|
|
22
|
+
children: /* @__PURE__ */ jsx("h2", {
|
|
23
|
+
className: "truncate text-sm font-medium",
|
|
24
|
+
children: "Workers"
|
|
25
|
+
})
|
|
26
|
+
});
|
|
27
|
+
t3 = /* @__PURE__ */ jsx("div", {
|
|
28
|
+
className: cn("bg-background text-muted-foreground flex min-h-0 flex-1 items-center justify-center p-4 text-sm", CARD_BODY_EDGES),
|
|
29
|
+
children: "No workers yet"
|
|
30
|
+
});
|
|
31
|
+
$[2] = t2;
|
|
32
|
+
$[3] = t3;
|
|
33
|
+
} else {
|
|
34
|
+
t2 = $[2];
|
|
35
|
+
t3 = $[3];
|
|
36
|
+
}
|
|
37
|
+
let t4;
|
|
38
|
+
if ($[4] !== t1) {
|
|
39
|
+
t4 = /* @__PURE__ */ jsxs("section", {
|
|
40
|
+
"aria-label": "Workers",
|
|
41
|
+
"data-workers-widget": true,
|
|
42
|
+
className: t1,
|
|
43
|
+
children: [t2, t3]
|
|
44
|
+
});
|
|
45
|
+
$[4] = t1;
|
|
46
|
+
$[5] = t4;
|
|
47
|
+
} else t4 = $[5];
|
|
48
|
+
return t4;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
export { WorkersWidget };
|
|
@@ -9,7 +9,7 @@ const studioStore = createStore({
|
|
|
9
9
|
open: false,
|
|
10
10
|
startRenaming: false
|
|
11
11
|
},
|
|
12
|
-
|
|
12
|
+
isDeleteDraftDialogOpen: false,
|
|
13
13
|
localeDialog: null,
|
|
14
14
|
websiteLocation: null
|
|
15
15
|
},
|
|
@@ -64,16 +64,16 @@ const studioStore = createStore({
|
|
|
64
64
|
}
|
|
65
65
|
};
|
|
66
66
|
},
|
|
67
|
-
|
|
67
|
+
openDeleteDraftDialog: (context) => {
|
|
68
68
|
return {
|
|
69
69
|
...context,
|
|
70
|
-
|
|
70
|
+
isDeleteDraftDialogOpen: true
|
|
71
71
|
};
|
|
72
72
|
},
|
|
73
|
-
|
|
73
|
+
closeDeleteDraftDialog: (context) => {
|
|
74
74
|
return {
|
|
75
75
|
...context,
|
|
76
|
-
|
|
76
|
+
isDeleteDraftDialogOpen: false
|
|
77
77
|
};
|
|
78
78
|
},
|
|
79
79
|
openLocaleDialog: (context, event) => {
|
package/dist/lib/queries.js
CHANGED
|
@@ -43,7 +43,7 @@ const projectMutations = {
|
|
|
43
43
|
};
|
|
44
44
|
const draftMutations = {
|
|
45
45
|
publish: () => getOrpc().drafts.publish.mutationOptions(),
|
|
46
|
-
|
|
46
|
+
delete: () => getOrpc().drafts.delete.mutationOptions(),
|
|
47
47
|
/** Requests the draft's overview again right away, once it failed. */
|
|
48
48
|
retryOverview: () => getOrpc().drafts.retryOverview.mutationOptions(),
|
|
49
49
|
/** Drops the draft's copy of an entity it conflicts on, to use Live's version instead. */
|
package/dist/package.js
CHANGED