@hraness/slopcamera 3.10.2 → 3.10.3

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/README.md CHANGED
@@ -365,15 +365,3 @@ to support development and opens no browser. `support dismiss` opts out across
365
365
  the local suite, `support snooze` pauses the notice for 30 days, and
366
366
  `HRANESS_SUPPORT_AUDIENCE=off` disables ambient notices. Agents follow the
367
367
  [support reference](skills/slopcamera/references/support.md).
368
-
369
- ## Former name
370
-
371
- <details>
372
- <summary>Historical Atet release evidence</summary>
373
-
374
- SlopCamera was called Atet until September 2026.
375
- [Atet v3.2.3](https://github.com/hraness/atet/releases/tag/v3.2.3) and its
376
- [original archive](https://github.com/hraness/atet/releases/download/v3.2.3/hraness-atet-3.2.3.tgz)
377
- still install Atet, not SlopCamera.
378
-
379
- </details>
@@ -4,7 +4,7 @@ import type { z } from "zod";
4
4
 
5
5
  import type { ApplicationContext } from "./context";
6
6
 
7
- export const SLOPCAMERA_APPLICATION_TOOL_VERSION = "slopcamera-3.10.2" as const;
7
+ export const SLOPCAMERA_APPLICATION_TOOL_VERSION = "slopcamera-3.10.3" as const;
8
8
 
9
9
  export const OPERATION_KINDS = [
10
10
  "scene.audit",
@@ -93,6 +93,31 @@ function isWithin(root: string, candidate: string): boolean {
93
93
  return pathRelative === "" || (!pathRelative.startsWith("..") && !isAbsolute(pathRelative));
94
94
  }
95
95
 
96
+ function svgRootAttributes(contents: string): string | undefined {
97
+ let offset = 0;
98
+ const whitespace = /\s+/uy;
99
+ const skipWhitespace = (): void => {
100
+ whitespace.lastIndex = offset;
101
+ offset += whitespace.exec(contents)?.[0].length ?? 0;
102
+ };
103
+ skipWhitespace();
104
+ const declaration = /<\?xml[^>]*>\s*/iuy;
105
+ declaration.lastIndex = offset;
106
+ offset += declaration.exec(contents)?.[0].length ?? 0;
107
+
108
+ // Consume each comment once. A repeated lazy regex can reconsider every
109
+ // earlier comment terminator when the SVG root is missing.
110
+ while (contents.startsWith("<!--", offset)) {
111
+ const end = contents.indexOf("-->", offset + 4);
112
+ if (end === -1) return undefined;
113
+ offset = end + 3;
114
+ skipWhitespace();
115
+ }
116
+ const root = /<svg(?=[\s>])([^>]*)>/iuy;
117
+ root.lastIndex = offset;
118
+ return root.exec(contents)?.[1];
119
+ }
120
+
96
121
  function validateSvg(bytes: Uint8Array): boolean {
97
122
  if (bytes.length > MAX_SVG_BYTES) return false;
98
123
  let contents: string;
@@ -101,8 +126,7 @@ function validateSvg(bytes: Uint8Array): boolean {
101
126
  } catch {
102
127
  return false;
103
128
  }
104
- const start = contents.trimStart();
105
- if (!/^(?:<\?xml[^>]*>\s*)?(?:<!--[\s\S]*?-->\s*)*<svg(?:\s|>)/iu.test(start)) return false;
129
+ if (svgRootAttributes(contents) === undefined) return false;
106
130
  if (
107
131
  /<!DOCTYPE|<!ENTITY|<\?xml-stylesheet|<\s*(?:script|foreignObject|iframe|object|embed)\b/iu.test(contents)
108
132
  || /\s+on[a-z][a-z0-9_-]*\s*=/iu.test(contents)
@@ -227,13 +251,13 @@ export function parseSvgIntrinsicSize(bytes: Uint8Array): OverlayIntrinsicSize {
227
251
  throw new CliError("invalid-data", "SVG overlay is not valid UTF-8.");
228
252
  }
229
253
  if (!validateSvg(bytes)) throw new CliError("invalid-data", "SVG overlay failed the safe SVG profile.");
230
- const root = /^(?:<\?xml[^>]*>\s*)?(?:<!--[\s\S]*?-->\s*)*<svg\b(?<attributes>[^>]*)>/iu.exec(contents.trimStart());
231
- if (root?.groups?.attributes === undefined) {
254
+ const attributes = svgRootAttributes(contents);
255
+ if (attributes === undefined) {
232
256
  throw new CliError("invalid-data", "SVG overlay omits its root element.");
233
257
  }
234
- const width = svgLength(svgAttribute(root.groups.attributes, "width"));
235
- const height = svgLength(svgAttribute(root.groups.attributes, "height"));
236
- const viewBox = svgAttribute(root.groups.attributes, "viewBox")
258
+ const width = svgLength(svgAttribute(attributes, "width"));
259
+ const height = svgLength(svgAttribute(attributes, "height"));
260
+ const viewBox = svgAttribute(attributes, "viewBox")
237
261
  ?.split(/[\s,]+/u)
238
262
  .filter(Boolean)
239
263
  .map(Number);
@@ -365,7 +365,7 @@ import {
365
365
  workflowRunStore,
366
366
  } from "./workflow-runs";
367
367
 
368
- export const SLOPCAMERA_VERSION = "3.10.2";
368
+ export const SLOPCAMERA_VERSION = "3.10.3";
369
369
 
370
370
  // Legacy direct renders predate per-target output contracts. Keep them
371
371
  // bounded generously enough for long-form production while preventing one