@agent-native/core 0.81.1 → 0.81.2

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 (43) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/server/core-routes-plugin.ts +4 -5
  5. package/corpus/core/src/server/create-server.ts +3 -5
  6. package/corpus/core/src/server/google-oauth.ts +4 -0
  7. package/corpus/templates/calendar/actions/create-event.ts +9 -2
  8. package/corpus/templates/calendar/actions/event-action-helpers.ts +8 -2
  9. package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +11 -0
  10. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +17 -0
  11. package/corpus/templates/calendar/app/hooks/use-google-auth.ts +70 -39
  12. package/corpus/templates/calendar/changelog/2026-06-30-google-calendar-connection-errors-now-stay-in-the-app-instea.md +6 -0
  13. package/corpus/templates/calendar/server/handlers/google-auth.ts +77 -21
  14. package/corpus/templates/calendar/server/lib/google-calendar.ts +19 -8
  15. package/corpus/templates/design/actions/apply-design-token-edit.ts +13 -6
  16. package/corpus/templates/design/actions/import-design-tokens.ts +17 -13
  17. package/corpus/templates/design/actions/preview-design-token-edit.ts +20 -3
  18. package/corpus/templates/design/actions/run-design-audit.ts +9 -2
  19. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +35 -14
  20. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +20 -3
  21. package/corpus/templates/design/app/pages/DesignEditor.tsx +237 -51
  22. package/corpus/templates/design/app/pages/VisualEdit.tsx +1 -1
  23. package/corpus/templates/design/changelog/2026-06-30-design-editor-undo-drag-drop-style-editing-and-visual-edit-s.md +6 -0
  24. package/corpus/templates/design/shared/resolve-tweaks.ts +29 -6
  25. package/dist/collab/routes.d.ts +1 -1
  26. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  27. package/dist/notifications/routes.d.ts +1 -1
  28. package/dist/observability/routes.d.ts +7 -7
  29. package/dist/progress/routes.d.ts +1 -1
  30. package/dist/resources/handlers.d.ts +3 -3
  31. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  32. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  33. package/dist/server/core-routes-plugin.js +1 -2
  34. package/dist/server/core-routes-plugin.js.map +1 -1
  35. package/dist/server/create-server.d.ts.map +1 -1
  36. package/dist/server/create-server.js +1 -2
  37. package/dist/server/create-server.js.map +1 -1
  38. package/dist/server/google-oauth.d.ts +2 -0
  39. package/dist/server/google-oauth.d.ts.map +1 -1
  40. package/dist/server/google-oauth.js +3 -0
  41. package/dist/server/google-oauth.js.map +1 -1
  42. package/dist/server/transcribe-voice.d.ts +1 -1
  43. package/package.json +1 -1
@@ -10,7 +10,11 @@ import {
10
10
  INLINE_DEFAULT_CAPABILITIES,
11
11
  hasCapability,
12
12
  } from "../shared/design-source-capabilities.js";
13
- import { resolveTweaksToCssVars } from "../shared/resolve-tweaks.js";
13
+ import {
14
+ isSafeCssTokenValue,
15
+ isSafeCssVarName,
16
+ resolveTweaksToCssVars,
17
+ } from "../shared/resolve-tweaks.js";
14
18
 
15
19
  // ---------------------------------------------------------------------------
16
20
  // Token-edit patch schema (mirrors preview-design-token-edit)
@@ -23,17 +27,20 @@ const tokenEditSchema = z.object({
23
27
  .startsWith("--")
24
28
  // The token name is spliced raw into a `:root { … }` rule, so constrain it
25
29
  // to a safe CSS custom-property ident (leading "--" plus ident chars only).
26
- .regex(
27
- /^--[-_a-zA-Z0-9]+$/,
30
+ .refine(
31
+ isSafeCssVarName,
28
32
  "cssVar must be a valid CSS custom property name (-- followed by letters, digits, hyphens, or underscores).",
29
33
  )
30
34
  .describe("CSS custom property to edit"),
31
35
  /** New value string, e.g. "#3B82F6" or "0.75rem". */
32
36
  value: z
33
37
  .string()
34
- // The value is spliced raw into the `:root { … }` block; reject characters
35
- // that could terminate the rule (`}`) or break out of a `<style>` (`<`).
36
- .refine((v) => !/[}<]/.test(v), 'Token value may not contain "}" or "<".')
38
+ // The value is rendered into CSS custom-property declarations; reject
39
+ // declaration terminators and style-tag breakout characters.
40
+ .refine(
41
+ isSafeCssTokenValue,
42
+ 'Token value may not contain ";", "{", "}", "<", ">", CSS comments, or control characters.',
43
+ )
37
44
  .describe("New value for the token"),
38
45
  });
39
46
 
@@ -17,7 +17,11 @@ import { z } from "zod";
17
17
 
18
18
  import { getDb, schema } from "../server/db/index.js";
19
19
  import "../server/db/index.js"; // ensure registerShareableResource runs
20
- import { resolveTweaksToCssVars } from "../shared/resolve-tweaks.js";
20
+ import {
21
+ isSafeCssTokenValue,
22
+ isSafeCssVarName,
23
+ resolveTweaksToCssVars,
24
+ } from "../shared/resolve-tweaks.js";
21
25
 
22
26
  type ImportedTokenType =
23
27
  | "color"
@@ -174,10 +178,6 @@ function normalizeValue(value: string): string {
174
178
  .trim();
175
179
  }
176
180
 
177
- function isSafeTokenValue(value: string): boolean {
178
- return value.length > 0 && value.length <= 300 && !/[}<]/.test(value);
179
- }
180
-
181
181
  function tokenVar(prefix: string, key: string): string {
182
182
  if (key.startsWith("--")) return key;
183
183
  const stem = toKebab(key);
@@ -224,7 +224,7 @@ function parseNamedLines(
224
224
 
225
225
  const label = match[1].replace(/\*\*/g, "").trim();
226
226
  const value = normalizeValue(match[2]);
227
- if (!isSafeTokenValue(value)) continue;
227
+ if (!isSafeCssTokenValue(value)) continue;
228
228
 
229
229
  const lower = label.toLowerCase();
230
230
  let cssVar: string | null = null;
@@ -261,7 +261,7 @@ function uniqueTokenList(tokens: ImportedDesignToken[]): ImportedDesignToken[] {
261
261
 
262
262
  for (const token of tokens) {
263
263
  if (!/^--[-_a-zA-Z0-9]+$/.test(token.cssVar)) continue;
264
- if (!isSafeTokenValue(token.value)) continue;
264
+ if (!isSafeCssTokenValue(token.value)) continue;
265
265
  const previous = byVar.get(token.cssVar);
266
266
  byVar.set(token.cssVar, token);
267
267
  if (!previous && !/^--color-imported-\d+$/.test(token.cssVar)) {
@@ -404,7 +404,11 @@ export default defineAction({
404
404
  }
405
405
 
406
406
  const { filesAnalyzed, tokens } = tokensFromFiles(importFiles);
407
- if (tokens.length === 0) {
407
+ const safeTokens = tokens.filter(
408
+ (token) =>
409
+ isSafeCssVarName(token.cssVar) && isSafeCssTokenValue(token.value),
410
+ );
411
+ if (safeTokens.length === 0) {
408
412
  return {
409
413
  designId,
410
414
  source,
@@ -452,7 +456,7 @@ export default defineAction({
452
456
  : {};
453
457
  const nextSelections = { ...prevSelections };
454
458
 
455
- for (const token of tokens) {
459
+ for (const token of safeTokens) {
456
460
  const tweakId = cssVarToTweakId.get(token.cssVar);
457
461
  nextSelections[tweakId ?? token.cssVar] = token.value;
458
462
  }
@@ -464,7 +468,7 @@ export default defineAction({
464
468
  ? (prevData.tokenImportSources as Record<string, string>)
465
469
  : {};
466
470
  const tokenImportSources = { ...previousSources };
467
- for (const token of tokens) {
471
+ for (const token of safeTokens) {
468
472
  tokenImportSources[token.cssVar] =
469
473
  token.source === "CSS variables" || token.source === "Colors"
470
474
  ? sourceLabel
@@ -484,7 +488,7 @@ export default defineAction({
484
488
  {
485
489
  source,
486
490
  sourceLabel,
487
- tokenCount: tokens.length,
491
+ tokenCount: safeTokens.length,
488
492
  filesAnalyzed,
489
493
  importedAt: now,
490
494
  },
@@ -500,10 +504,10 @@ export default defineAction({
500
504
  return {
501
505
  designId,
502
506
  source,
503
- importedCount: tokens.length,
507
+ importedCount: safeTokens.length,
504
508
  skippedCount: importFiles.length - filesAnalyzed.length,
505
509
  filesAnalyzed,
506
- tokens,
510
+ tokens: safeTokens,
507
511
  resolvedCssVars: resolveTweaksToCssVars(tweaks, nextSelections),
508
512
  deepLink: designDeepLink(designId),
509
513
  };
@@ -3,7 +3,11 @@ import { resolveAccess } from "@agent-native/core/sharing";
3
3
  import { z } from "zod";
4
4
 
5
5
  import "../server/db/index.js"; // ensure registerShareableResource runs
6
- import { resolveTweaksToCssVars } from "../shared/resolve-tweaks.js";
6
+ import {
7
+ isSafeCssTokenValue,
8
+ isSafeCssVarName,
9
+ resolveTweaksToCssVars,
10
+ } from "../shared/resolve-tweaks.js";
7
11
 
8
12
  // ---------------------------------------------------------------------------
9
13
  // Token-edit patch schema
@@ -11,9 +15,22 @@ import { resolveTweaksToCssVars } from "../shared/resolve-tweaks.js";
11
15
 
12
16
  const tokenEditSchema = z.object({
13
17
  /** The CSS custom property to update, e.g. "--primary-color". */
14
- cssVar: z.string().startsWith("--").describe("CSS custom property to edit"),
18
+ cssVar: z
19
+ .string()
20
+ .startsWith("--")
21
+ .refine(
22
+ isSafeCssVarName,
23
+ "cssVar must be a valid CSS custom property name (-- followed by letters, digits, hyphens, or underscores).",
24
+ )
25
+ .describe("CSS custom property to edit"),
15
26
  /** New value string, e.g. "#3B82F6" or "0.75rem". */
16
- value: z.string().describe("New value for the token"),
27
+ value: z
28
+ .string()
29
+ .refine(
30
+ isSafeCssTokenValue,
31
+ 'Token value may not contain ";", "{", "}", "<", ">", CSS comments, or control characters.',
32
+ )
33
+ .describe("New value for the token"),
17
34
  });
18
35
 
19
36
  // ---------------------------------------------------------------------------
@@ -196,13 +196,20 @@ export function checkTapTargets(html: string): A11yFinding[] {
196
196
  const findings: A11yFinding[] = [];
197
197
  // Heuristic: buttons/links with explicit tiny size classes (h-4, h-5, w-4, w-5, size-4, size-5)
198
198
  // and no explicit larger override or sr-only are flagged.
199
- const interactivePattern = /<(?:button|a)\b[^>]*>/gi;
199
+ const interactivePattern =
200
+ /<(button|a|input|select|textarea)\b[^>]*(?:\/>|>)/gi;
200
201
  const tinyPattern = /\b(?:h|w|size)-[345]\b/;
201
202
  const largePattern = /\b(?:h|w|size)-(?:[6-9]|[1-9]\d)/;
202
203
  const srOnlyPattern = /\bsr-only\b/;
203
204
  let idx = 0;
204
205
  for (const m of html.matchAll(interactivePattern)) {
205
206
  const tag = m[0];
207
+ const tagName = (m[1] ?? "button").toLowerCase();
208
+ const typeMatch = tag.match(/\btype\s*=\s*(?:"([^"]*?)"|'([^']*?)')/i);
209
+ if (tagName === "input") {
210
+ const type = (typeMatch?.[1] ?? typeMatch?.[2] ?? "text").toLowerCase();
211
+ if (type === "hidden") continue;
212
+ }
206
213
  if (
207
214
  tinyPattern.test(tag) &&
208
215
  !largePattern.test(tag) &&
@@ -217,7 +224,7 @@ export function checkTapTargets(html: string): A11yFinding[] {
217
224
  detail:
218
225
  "Minimum recommended tap target size is 44×44 px (WCAG 2.5.5). Consider increasing padding or size.",
219
226
  nodeId: extractNodeId(tag),
220
- selector: extractSelector(tag, "button"),
227
+ selector: extractSelector(tag, tagName),
221
228
  wcag: "2.5.5",
222
229
  fixAvailable: true,
223
230
  });
@@ -755,6 +755,7 @@ export function DesignColorPicker({
755
755
 
756
756
  const [mode, setMode] = useState<DesignColorMode>("hex");
757
757
  const [hexDraft, setHexDraft] = useState(() => toDisplayHex(color));
758
+ const hexDraftRef = useRef(hexDraft);
758
759
  const [open, setOpen] = useState(false);
759
760
  const [picking, setPicking] = useState(false);
760
761
  const skipNextHexBlurCommitRef = useRef(false);
@@ -814,7 +815,9 @@ export function DesignColorPicker({
814
815
  : null;
815
816
 
816
817
  useEffect(() => {
817
- setHexDraft(toDisplayHex(color));
818
+ const nextHex = toDisplayHex(color);
819
+ hexDraftRef.current = nextHex;
820
+ setHexDraft(nextHex);
818
821
  }, [color.r, color.g, color.b]);
819
822
 
820
823
  // The local override (the user's explicit paint-type click) persists for the
@@ -863,17 +866,20 @@ export function DesignColorPicker({
863
866
  };
864
867
 
865
868
  const commitHex = () => {
866
- const parsed = parseCssColor(`#${hexDraft.replace(/^#/, "")}`);
869
+ const currentDraft = hexDraftRef.current;
870
+ const parsed = parseCssColor(`#${currentDraft.replace(/^#/, "")}`);
867
871
  if (!parsed) {
868
- setHexDraft(toDisplayHex(activeGradient ? fieldColor : color));
872
+ const reverted = toDisplayHex(activeGradient ? fieldColor : color);
873
+ hexDraftRef.current = reverted;
874
+ setHexDraft(reverted);
869
875
  return;
870
876
  }
871
877
  if (activeGradient) {
872
- const hexIncludesAlpha = hasHexAlpha(hexDraft);
878
+ const hexIncludesAlpha = hasHexAlpha(currentDraft);
873
879
  emitStopColor(hexIncludesAlpha ? parsed : { ...parsed, a: fieldColor.a });
874
880
  return;
875
881
  }
876
- const hexIncludesAlpha = hasHexAlpha(hexDraft);
882
+ const hexIncludesAlpha = hasHexAlpha(currentDraft);
877
883
  const nextOpacity = hexIncludesAlpha
878
884
  ? alphaToOpacity(parsed.a)
879
885
  : effectiveOpacity;
@@ -1080,7 +1086,10 @@ export function DesignColorPicker({
1080
1086
  aria-label={copy.hex}
1081
1087
  spellCheck={false}
1082
1088
  className="h-6 min-w-0 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-2 text-[11px] tabular-nums uppercase"
1083
- onChange={(e) => setHexDraft(e.target.value)}
1089
+ onChange={(e) => {
1090
+ hexDraftRef.current = e.target.value;
1091
+ setHexDraft(e.target.value);
1092
+ }}
1084
1093
  onFocus={(e) => e.target.select()}
1085
1094
  onKeyDown={(e) => {
1086
1095
  if (e.key === "Enter") {
@@ -1090,7 +1099,9 @@ export function DesignColorPicker({
1090
1099
  e.currentTarget.blur();
1091
1100
  }
1092
1101
  if (e.key === "Escape") {
1093
- setHexDraft(toDisplayHex(color));
1102
+ const reverted = toDisplayHex(color);
1103
+ hexDraftRef.current = reverted;
1104
+ setHexDraft(reverted);
1094
1105
  skipNextHexBlurCommitRef.current = true;
1095
1106
  e.currentTarget.blur();
1096
1107
  }
@@ -1978,16 +1989,21 @@ function ScrubbyNumberInput({
1978
1989
  compact?: boolean;
1979
1990
  }) {
1980
1991
  const [draft, setDraft] = useState<string>(() => String(value));
1992
+ const draftRef = useRef(draft);
1981
1993
  const skipBlurRef = useRef(false);
1982
1994
 
1983
1995
  useEffect(() => {
1984
- setDraft(String(value));
1996
+ const nextDraft = String(value);
1997
+ draftRef.current = nextDraft;
1998
+ setDraft(nextDraft);
1985
1999
  }, [value]);
1986
2000
 
1987
2001
  const commit = () => {
1988
- const parsed = Number(draft);
2002
+ const parsed = Number(draftRef.current);
1989
2003
  if (!Number.isFinite(parsed)) {
1990
- setDraft(String(value));
2004
+ const reverted = String(value);
2005
+ draftRef.current = reverted;
2006
+ setDraft(reverted);
1991
2007
  return;
1992
2008
  }
1993
2009
  onChange(clamp(parsed, min, max));
@@ -2007,7 +2023,10 @@ function ScrubbyNumberInput({
2007
2023
  compact && "border-0 shadow-none focus-visible:ring-0",
2008
2024
  className,
2009
2025
  )}
2010
- onChange={(e) => setDraft(e.target.value)}
2026
+ onChange={(e) => {
2027
+ draftRef.current = e.target.value;
2028
+ setDraft(e.target.value);
2029
+ }}
2011
2030
  onFocus={(e) => e.target.select()}
2012
2031
  onKeyDown={(e) => {
2013
2032
  if (e.key === "Enter") {
@@ -2017,21 +2036,23 @@ function ScrubbyNumberInput({
2017
2036
  e.currentTarget.blur();
2018
2037
  }
2019
2038
  if (e.key === "Escape") {
2020
- setDraft(String(value));
2039
+ const reverted = String(value);
2040
+ draftRef.current = reverted;
2041
+ setDraft(reverted);
2021
2042
  skipBlurRef.current = true;
2022
2043
  e.currentTarget.blur();
2023
2044
  }
2024
2045
  if (e.key === "ArrowUp") {
2025
2046
  e.preventDefault();
2026
2047
  const step = e.shiftKey ? 10 : 1;
2027
- const parsed = Number(draft);
2048
+ const parsed = Number(draftRef.current);
2028
2049
  const base = Number.isFinite(parsed) ? parsed : value;
2029
2050
  onChange(clamp(base + step, min, max));
2030
2051
  }
2031
2052
  if (e.key === "ArrowDown") {
2032
2053
  e.preventDefault();
2033
2054
  const step = e.shiftKey ? 10 : 1;
2034
- const parsed = Number(draft);
2055
+ const parsed = Number(draftRef.current);
2035
2056
  const base = Number.isFinite(parsed) ? parsed : value;
2036
2057
  onChange(clamp(base - step, min, max));
2037
2058
  }
@@ -238,6 +238,14 @@ export function parseImageFillCss(
238
238
  return { url, fit };
239
239
  }
240
240
 
241
+ export function mergeImageFitDraft(
242
+ value: ImageFillValue,
243
+ urlDraft: string,
244
+ fit: ImageFitMode,
245
+ ): ImageFillValue {
246
+ return { ...value, url: urlDraft.trim(), fit };
247
+ }
248
+
241
249
  // ─── Component ─────────────────────────────────────────────────────────────────
242
250
 
243
251
  export interface ImageFillControlsProps {
@@ -255,13 +263,15 @@ export function ImageFillControls({
255
263
  }: ImageFillControlsProps) {
256
264
  const fileInputRef = useRef<HTMLInputElement>(null);
257
265
  const [urlDraft, setUrlDraft] = useState(value.url);
266
+ const urlDraftRef = useRef(value.url);
258
267
 
259
268
  useEffect(() => {
269
+ urlDraftRef.current = value.url;
260
270
  setUrlDraft(value.url);
261
271
  }, [value.url]);
262
272
 
263
273
  const commitUrl = () => {
264
- onChange({ ...value, url: urlDraft.trim() });
274
+ onChange({ ...value, url: urlDraftRef.current.trim() });
265
275
  };
266
276
 
267
277
  const handleFilePick = (event: React.ChangeEvent<HTMLInputElement>) => {
@@ -338,7 +348,10 @@ export function ImageFillControls({
338
348
  aria-label={"Image URL" /* i18n-ignore */}
339
349
  spellCheck={false}
340
350
  className="h-6 min-w-0 flex-1 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-2 text-[11px]"
341
- onChange={(event) => setUrlDraft(event.target.value)}
351
+ onChange={(event) => {
352
+ urlDraftRef.current = event.target.value;
353
+ setUrlDraft(event.target.value);
354
+ }}
342
355
  onBlur={commitUrl}
343
356
  onKeyDown={(event) => {
344
357
  if (event.key === "Enter") {
@@ -378,7 +391,11 @@ export function ImageFillControls({
378
391
  {/* ── Fit mode dropdown ─────────────────────────────────────────────── */}
379
392
  <Select
380
393
  value={value.fit}
381
- onValueChange={(v) => onChange({ ...value, fit: v as ImageFitMode })}
394
+ onValueChange={(v) =>
395
+ onChange(
396
+ mergeImageFitDraft(value, urlDraftRef.current, v as ImageFitMode),
397
+ )
398
+ }
382
399
  disabled={disabled}
383
400
  >
384
401
  <SelectTrigger