@toolpath/dfm 0.2.0 → 0.3.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/index.js CHANGED
@@ -37,7 +37,7 @@ import {
37
37
  } from "./chunk-5MPHT7BC.js";
38
38
 
39
39
  // src/rule-list/rule-list.tsx
40
- import { useMemo as useMemo2, useState as useState7 } from "react";
40
+ import { useMemo as useMemo2, useState as useState8 } from "react";
41
41
  import { ScrollArea } from "@toolpath/ui";
42
42
 
43
43
  // src/rule-list/list-keys.ts
@@ -70,48 +70,31 @@ var currentIndex = (items, active) => {
70
70
  var isField = (target) => target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement || target instanceof HTMLInputElement && target.type !== "color" && target.type !== "button" || target instanceof HTMLElement && target.isContentEditable;
71
71
 
72
72
  // src/rule-list/new-rule.tsx
73
- import { useState } from "react";
74
- import { cn as cn2 } from "@toolpath/ui";
73
+ import { useState as useState2 } from "react";
75
74
 
76
- // src/rule-list/options.tsx
77
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
78
- var MORE = "__more";
79
- var showMore = (select, setAll) => {
80
- setAll(true);
81
- requestAnimationFrame(() => {
82
- try {
83
- select.showPicker();
84
- } catch {
85
- select.focus();
86
- }
87
- });
88
- };
89
- var option = (value, label) => /* @__PURE__ */ jsx("option", { value, children: label }, value);
90
- var SubjectOptions = ({
91
- current,
92
- all
93
- }) => {
75
+ // src/rule-list/options.ts
76
+ var subjectChoices = (current, all) => {
94
77
  const quick = [...QUICK_SUBJECTS, ...QUICK_SUBJECTS.includes(current) ? [] : [current]];
78
+ const first = { choices: quick.map((value) => ({ value, label: subjectLabel(value) })) };
79
+ if (!all) return { groups: [first], more: "More feature types\u2026" };
95
80
  const rest = FEATURE_SUBJECTS.filter((each) => !quick.includes(each.value));
96
- return /* @__PURE__ */ jsxs(Fragment, { children: [
97
- quick.map((value) => option(value, subjectLabel(value))),
98
- all ? /* @__PURE__ */ jsx("optgroup", { label: "More feature types", children: rest.map((each) => option(each.value, each.label)) }) : option(MORE, "More feature types\u2026")
99
- ] });
81
+ return { groups: [first, { heading: "More feature types", choices: rest }] };
100
82
  };
101
- var MetricOptions = ({
102
- subject,
103
- current,
104
- all
105
- }) => {
83
+ var metricChoices = (subject, current, all) => {
106
84
  const offered = metricsForSubject(subject);
107
85
  const quick = offered.filter((each) => QUICK_METRICS.includes(each.key) || each.key === current);
108
86
  quick.sort((a, b) => rank(a.key) - rank(b.key));
109
87
  const rest = offered.filter((each) => !quick.includes(each));
110
- return /* @__PURE__ */ jsxs(Fragment, { children: [
111
- quick.map((each) => option(each.key, each.label)),
112
- all ? grouped(rest).map(([group, metrics]) => /* @__PURE__ */ jsx("optgroup", { label: group, children: metrics.map((each) => option(each.key, each.label)) }, group)) : rest.length > 0 ? option(MORE, "More measures\u2026") : null
113
- ] });
88
+ const first = { choices: quick.map(choice) };
89
+ if (!all) return { groups: [first], more: rest.length > 0 ? "More measures\u2026" : void 0 };
90
+ return {
91
+ groups: [
92
+ first,
93
+ ...grouped(rest).map(([heading, metrics]) => ({ heading, choices: metrics.map(choice) }))
94
+ ]
95
+ };
114
96
  };
97
+ var choice = ({ key, label }) => ({ value: key, label });
115
98
  var rank = (key) => {
116
99
  const at = QUICK_METRICS.indexOf(key);
117
100
  return at === -1 ? QUICK_METRICS.length : at;
@@ -122,6 +105,17 @@ var grouped = (items) => {
122
105
  return [...groups];
123
106
  };
124
107
 
108
+ // src/rule-list/picker.tsx
109
+ import {
110
+ Fragment,
111
+ useEffect,
112
+ useId,
113
+ useRef,
114
+ useState
115
+ } from "react";
116
+ import { CheckIcon } from "@phosphor-icons/react";
117
+ import { Menu, cn as cn2 } from "@toolpath/ui";
118
+
125
119
  // src/rule-list/styles.ts
126
120
  import { cn } from "@toolpath/ui";
127
121
  var FOCUS_RING = "outline-none focus-visible:ring-2 focus-visible:ring-info/75";
@@ -132,10 +126,157 @@ var WORD = cn(
132
126
  "outline-none focus-visible:bg-gray-100 focus-visible:ring-2 focus-visible:ring-info/75 dark:focus-visible:bg-zinc-700"
133
127
  );
134
128
 
129
+ // src/rule-list/picker.tsx
130
+ import { jsx, jsxs } from "react/jsx-runtime";
131
+ var HEADING = "px-2 pt-1.5 pb-1 text-[10px] font-semibold tracking-wider text-gray-300 uppercase font-body dark:text-zinc-500";
132
+ var Picker = ({
133
+ label,
134
+ value,
135
+ choices,
136
+ onChange,
137
+ onMore,
138
+ placeholder,
139
+ title: title2,
140
+ className,
141
+ autoFocus
142
+ }) => {
143
+ const id = useId();
144
+ const held = useRef(null);
145
+ const [holding, setHolding] = useState(0);
146
+ const hold = (next) => {
147
+ held.current = next;
148
+ if (next) setHolding((count) => count + 1);
149
+ };
150
+ const flat = choices.groups.flatMap((group) => group.choices);
151
+ const at = flat.findIndex((choice2) => choice2.value === value);
152
+ const shown = flat[at]?.label ?? placeholder ?? value;
153
+ const starts = choices.groups.map(
154
+ (_, g) => choices.groups.slice(0, g).reduce((count, group) => count + group.choices.length, 0)
155
+ );
156
+ useEffect(() => {
157
+ if (holding === 0) return;
158
+ const frame = requestAnimationFrame(() => focusHeld(id, held.current));
159
+ return () => cancelAnimationFrame(frame);
160
+ }, [holding, id]);
161
+ return (
162
+ // A click on the word, in its menu or on the menu's backdrop is the picker's: React would carry
163
+ // it up through the portal to the row, which opens on a click.
164
+ /* @__PURE__ */ jsx("span", { onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsxs(Menu, { onOpenChange: (open) => hold(open && at !== -1 ? { index: at, reveal: false } : null), children: [
165
+ /* @__PURE__ */ jsx(
166
+ Menu.Trigger,
167
+ {
168
+ nativeButton: true,
169
+ render: /* @__PURE__ */ jsx(
170
+ "button",
171
+ {
172
+ type: "button",
173
+ "aria-label": at === -1 ? label : `${label}: ${shown}`,
174
+ title: title2,
175
+ className: cn2(
176
+ WORD,
177
+ "data-[popup-open]:bg-gray-100 dark:data-[popup-open]:bg-zinc-700",
178
+ className
179
+ ),
180
+ autoFocus
181
+ }
182
+ ),
183
+ children: shown
184
+ }
185
+ ),
186
+ /* @__PURE__ */ jsxs(
187
+ Menu.Popover,
188
+ {
189
+ align: "start",
190
+ sideOffset: 4,
191
+ collisionPadding: 8,
192
+ className: "max-h-[min(24rem,var(--available-height))] min-w-[var(--anchor-width)] overflow-y-auto py-1 shadow-lg",
193
+ onFocus: (event) => {
194
+ const item = held.current && document.getElementById(itemId(id, held.current.index));
195
+ if (item && event.target !== item) focusHeld(id, held.current);
196
+ },
197
+ onPointerMove: () => hold(null),
198
+ onKeyDownCapture: () => hold(null),
199
+ onKeyDown: keepKeys,
200
+ children: [
201
+ choices.groups.map((group, g) => /* @__PURE__ */ jsxs(Fragment, { children: [
202
+ g > 0 ? /* @__PURE__ */ jsx(Menu.Divider, { className: "my-1" }) : null,
203
+ /* @__PURE__ */ jsxs(
204
+ "div",
205
+ {
206
+ role: "group",
207
+ "aria-labelledby": group.heading ? `${id}-group-${g}` : void 0,
208
+ className: "flex flex-col",
209
+ children: [
210
+ group.heading ? /* @__PURE__ */ jsx("div", { id: `${id}-group-${g}`, className: HEADING, children: group.heading }) : null,
211
+ group.choices.map((choice2, c) => {
212
+ const chosen = choice2.value === value;
213
+ return /* @__PURE__ */ jsxs(
214
+ Menu.Item,
215
+ {
216
+ id: itemId(id, (starts[g] ?? 0) + c),
217
+ label: choice2.label,
218
+ onClick: () => {
219
+ if (!chosen) onChange(choice2.value);
220
+ },
221
+ className: "gap-3",
222
+ children: [
223
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: choice2.label }),
224
+ /* @__PURE__ */ jsx(
225
+ CheckIcon,
226
+ {
227
+ "aria-hidden": true,
228
+ weight: "bold",
229
+ className: cn2(
230
+ "size-3.5 text-gray-400 dark:text-zinc-300",
231
+ !chosen && "invisible"
232
+ )
233
+ }
234
+ )
235
+ ]
236
+ },
237
+ choice2.value
238
+ );
239
+ })
240
+ ]
241
+ }
242
+ )
243
+ ] }, group.heading ?? g)),
244
+ choices.more && onMore ? /* @__PURE__ */ jsx(
245
+ Menu.Item,
246
+ {
247
+ closeOnClick: false,
248
+ onClick: () => {
249
+ hold({ index: flat.length, reveal: true });
250
+ onMore();
251
+ },
252
+ className: "text-gray-400 dark:text-zinc-400",
253
+ children: choices.more
254
+ }
255
+ ) : null
256
+ ]
257
+ }
258
+ )
259
+ ] }) })
260
+ );
261
+ };
262
+ var focusHeld = (picker, held) => {
263
+ const item = held && document.getElementById(itemId(picker, held.index));
264
+ if (!item) return;
265
+ item.focus({ preventScroll: held.reveal });
266
+ const menu = item.closest('[role="menu"]');
267
+ const group = item.closest('[role="group"]');
268
+ if (held.reveal && menu && group)
269
+ menu.scrollTop += group.getBoundingClientRect().top - menu.getBoundingClientRect().top - 8;
270
+ };
271
+ var itemId = (picker, index) => `${picker}-${index}`;
272
+ var keepKeys = (event) => {
273
+ if (event.key !== "Tab") event.stopPropagation();
274
+ };
275
+
135
276
  // src/rule-list/new-rule.tsx
136
277
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
137
278
  var NewRule = ({ onAdd }) => {
138
- const [all, setAll] = useState(false);
279
+ const [all, setAll] = useState2(false);
139
280
  return /* @__PURE__ */ jsxs2("li", { className: "flex items-start gap-1.5 py-1 pr-1.5", children: [
140
281
  /* @__PURE__ */ jsx2("span", { "aria-hidden": true, className: "size-7 shrink-0" }),
141
282
  /* @__PURE__ */ jsx2(
@@ -145,27 +286,23 @@ var NewRule = ({ onAdd }) => {
145
286
  className: "mt-1.5 size-4 shrink-0 rounded-full border border-dashed border-gray-300 dark:border-zinc-600"
146
287
  }
147
288
  ),
148
- /* @__PURE__ */ jsx2("p", { className: "min-w-0 flex-1 text-sm leading-7", children: /* @__PURE__ */ jsxs2(
149
- "select",
289
+ /* @__PURE__ */ jsx2("p", { className: "min-w-0 flex-1 text-sm leading-7", children: /* @__PURE__ */ jsx2(
290
+ Picker,
150
291
  {
151
- "aria-label": "New rule",
152
- className: cn2(WORD, "font-normal text-gray-400 dark:text-zinc-500"),
292
+ label: "New rule",
293
+ placeholder: "+ New Rule",
153
294
  value: "",
154
- onChange: (event) => {
155
- if (event.target.value === MORE) showMore(event.currentTarget, setAll);
156
- else if (event.target.value) onAdd(event.target.value);
157
- },
158
- children: [
159
- /* @__PURE__ */ jsx2("option", { value: "", disabled: true, children: "+ New Rule" }),
160
- /* @__PURE__ */ jsx2(SubjectOptions, { current: "any", all })
161
- ]
295
+ choices: subjectChoices("any", all),
296
+ onChange: onAdd,
297
+ onMore: () => setAll(true),
298
+ className: "font-normal text-gray-400 dark:text-zinc-500"
162
299
  }
163
300
  ) })
164
301
  ] });
165
302
  };
166
303
 
167
304
  // src/rule-list/rule-row.tsx
168
- import { useRef as useRef2, useState as useState5 } from "react";
305
+ import { useRef as useRef3, useState as useState6 } from "react";
169
306
  import { CaretRightIcon as CaretRightIcon2, TrashIcon } from "@phosphor-icons/react";
170
307
  import { IconButton, cn as cn7 } from "@toolpath/ui";
171
308
 
@@ -247,7 +384,7 @@ var FeatureButton = ({
247
384
  };
248
385
 
249
386
  // src/rule-list/hole-group-row.tsx
250
- import { useRef, useState as useState2 } from "react";
387
+ import { useRef as useRef2, useState as useState3 } from "react";
251
388
  import { CaretRightIcon } from "@phosphor-icons/react";
252
389
  import { cn as cn4 } from "@toolpath/ui";
253
390
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
@@ -256,8 +393,8 @@ var HoleGroupRow = ({
256
393
  direction,
257
394
  ...context
258
395
  }) => {
259
- const [open, setOpen] = useState2(false);
260
- const groupRef = useRef(null);
396
+ const [open, setOpen] = useState3(false);
397
+ const groupRef = useRef2(null);
261
398
  const { rule, check, units, selectedGroup, featureOf, onInspect, onHover, onBack } = context;
262
399
  const [first] = row.tags;
263
400
  const chosen = selectedGroup?.[0] === first;
@@ -374,10 +511,9 @@ var FeatureRows = ({
374
511
  var directionOf = (feature) => feature ? directionLabel(feature.machiningDirection) : "";
375
512
 
376
513
  // src/rule-list/rule-sentence.tsx
377
- import { useState as useState4 } from "react";
514
+ import { useState as useState5 } from "react";
378
515
 
379
- // src/rule-list/ops.tsx
380
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
516
+ // src/rule-list/ops.ts
381
517
  var OP_TITLES = {
382
518
  gte: "at least",
383
519
  gt: "more than",
@@ -389,44 +525,50 @@ var OP_TITLES = {
389
525
  };
390
526
  var LOW_SYMBOLS = { gte: "\u2264", gt: "<" };
391
527
  var ONE_SIDED = "one:";
392
- var OneSidedOptions = () => /* @__PURE__ */ jsx6("optgroup", { label: "One-sided", children: OPS.filter((op) => op !== "between").map((op) => /* @__PURE__ */ jsxs6("option", { value: `${ONE_SIDED}${op}`, children: [
393
- opSymbol(op),
394
- " ",
395
- OP_TITLES[op]
396
- ] }, op)) });
528
+ var ONE_SIDED_CHOICES = {
529
+ heading: "One-sided",
530
+ choices: OPS.filter((op) => op !== "between").map((op) => ({
531
+ value: `${ONE_SIDED}${op}`,
532
+ label: `${opSymbol(op)} ${OP_TITLES[op]}`
533
+ }))
534
+ };
397
535
  var rangeEnd = (rule, chosen, hold) => chosen.startsWith(ONE_SIDED) ? withOp(rule, chosen.slice(ONE_SIDED.length)) : { ...rule, ...hold(chosen) };
398
536
 
399
537
  // src/rule-list/comparison.tsx
400
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
401
- var Comparison = ({ rule, ranged, onChange }) => ranged ? /* @__PURE__ */ jsxs7(
402
- "select",
538
+ import { jsx as jsx6 } from "react/jsx-runtime";
539
+ var COMPARISONS = {
540
+ groups: [{ choices: OPS.map((op) => ({ value: op, label: opSymbol(op) })) }]
541
+ };
542
+ var RANGE_HIGH = {
543
+ groups: [
544
+ { heading: "Range", choices: RANGE_HIGHS.map((op) => ({ value: op, label: opSymbol(op) })) },
545
+ ONE_SIDED_CHOICES
546
+ ]
547
+ };
548
+ var Comparison = ({ rule, ranged, onChange }) => ranged ? /* @__PURE__ */ jsx6(
549
+ Picker,
403
550
  {
404
- "aria-label": "High end",
551
+ label: "High end",
405
552
  title: OP_TITLES[highOf(rule)],
406
- className: WORD,
407
553
  value: highOf(rule),
408
- onChange: (event) => onChange(rangeEnd(rule, event.target.value, (op) => ({ highOp: op }))),
409
- children: [
410
- /* @__PURE__ */ jsx7("optgroup", { label: "Range", children: RANGE_HIGHS.map((op) => /* @__PURE__ */ jsx7("option", { value: op, children: opSymbol(op) }, op)) }),
411
- /* @__PURE__ */ jsx7(OneSidedOptions, {})
412
- ]
554
+ choices: RANGE_HIGH,
555
+ onChange: (value) => onChange(rangeEnd(rule, value, (op) => ({ highOp: op })))
413
556
  }
414
- ) : /* @__PURE__ */ jsx7(
415
- "select",
557
+ ) : /* @__PURE__ */ jsx6(
558
+ Picker,
416
559
  {
417
- "aria-label": "Comparison",
560
+ label: "Comparison",
418
561
  title: OP_TITLES[rule.op],
419
- className: WORD,
420
562
  value: rule.op,
421
- onChange: (event) => onChange(withOp(rule, event.target.value)),
422
- children: OPS.map((op) => /* @__PURE__ */ jsx7("option", { value: op, children: opSymbol(op) }, op))
563
+ choices: COMPARISONS,
564
+ onChange: (value) => onChange(withOp(rule, value))
423
565
  }
424
566
  );
425
567
 
426
568
  // src/rule-list/value-field.tsx
427
- import { useEffect, useState as useState3 } from "react";
569
+ import { useEffect as useEffect2, useState as useState4 } from "react";
428
570
  import { cn as cn5 } from "@toolpath/ui";
429
- import { jsx as jsx8 } from "react/jsx-runtime";
571
+ import { jsx as jsx7 } from "react/jsx-runtime";
430
572
  var ValueField = ({
431
573
  label,
432
574
  info,
@@ -435,13 +577,13 @@ var ValueField = ({
435
577
  onChange
436
578
  }) => {
437
579
  const shown = toField(info.quantity, value, units);
438
- const [text, setText] = useState3(shown);
439
- const [typing, setTyping] = useState3(false);
440
- useEffect(() => {
580
+ const [text, setText] = useState4(shown);
581
+ const [typing, setTyping] = useState4(false);
582
+ useEffect2(() => {
441
583
  if (!typing) setText(shown);
442
584
  }, [shown, typing]);
443
585
  const invalid = fromField(info, text, units) === null;
444
- return /* @__PURE__ */ jsx8(
586
+ return /* @__PURE__ */ jsx7(
445
587
  "input",
446
588
  {
447
589
  "aria-label": label,
@@ -468,7 +610,13 @@ var ValueField = ({
468
610
  };
469
611
 
470
612
  // src/rule-list/rule-sentence.tsx
471
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
613
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
614
+ var RANGE_LOW = {
615
+ groups: [
616
+ { heading: "Range", choices: RANGE_LOWS.map((op) => ({ value: op, label: LOW_SYMBOLS[op] })) },
617
+ ONE_SIDED_CHOICES
618
+ ]
619
+ };
472
620
  var RuleSentence = ({
473
621
  rule,
474
622
  units,
@@ -480,28 +628,25 @@ var RuleSentence = ({
480
628
  const presence = info?.quantity === "presence";
481
629
  const ranged = info !== void 0 && !flag && rule.op === "between";
482
630
  const unit = info ? quantityUnit(info.quantity, units) : "";
483
- const [allSubjects, setAllSubjects] = useState4(false);
484
- const [allMetrics, setAllMetrics] = useState4(false);
485
- const unitSpan = unit ? /* @__PURE__ */ jsx9("span", { className: unit === "\xB0" || unit === "%" ? "" : "pl-1", children: unit }) : null;
486
- return /* @__PURE__ */ jsxs8("p", { className: "min-w-0 flex-1 text-sm leading-7 text-gray-500 dark:text-zinc-400", children: [
487
- /* @__PURE__ */ jsx9(
488
- "select",
631
+ const [allSubjects, setAllSubjects] = useState5(false);
632
+ const [allMetrics, setAllMetrics] = useState5(false);
633
+ const unitSpan = unit ? /* @__PURE__ */ jsx8("span", { className: unit === "\xB0" || unit === "%" ? "" : "pl-1", children: unit }) : null;
634
+ return /* @__PURE__ */ jsxs6("p", { className: "min-w-0 flex-1 text-sm leading-7 text-gray-500 dark:text-zinc-400", children: [
635
+ /* @__PURE__ */ jsx8(
636
+ Picker,
489
637
  {
490
- "aria-label": "Features",
491
- className: WORD,
638
+ label: "Features",
492
639
  value: rule.subject,
493
- onChange: (event) => {
494
- if (event.target.value === MORE) showMore(event.currentTarget, setAllSubjects);
495
- else onChange(withSubject(rule, event.target.value, units));
496
- },
497
- children: /* @__PURE__ */ jsx9(SubjectOptions, { current: rule.subject, all: allSubjects })
640
+ choices: subjectChoices(rule.subject, allSubjects),
641
+ onChange: (value) => onChange(withSubject(rule, value, units)),
642
+ onMore: () => setAllSubjects(true)
498
643
  }
499
644
  ),
500
645
  presence ? ", " : " with ",
501
- /* @__PURE__ */ jsxs8("span", { className: ranged ? "whitespace-nowrap" : void 0, children: [
502
- ranged ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
503
- /* @__PURE__ */ jsxs8("span", { className: "whitespace-nowrap", children: [
504
- /* @__PURE__ */ jsx9(
646
+ /* @__PURE__ */ jsxs6("span", { className: ranged ? "whitespace-nowrap" : void 0, children: [
647
+ ranged ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
648
+ /* @__PURE__ */ jsxs6("span", { className: "whitespace-nowrap", children: [
649
+ /* @__PURE__ */ jsx8(
505
650
  ValueField,
506
651
  {
507
652
  label: "From",
@@ -514,43 +659,36 @@ var RuleSentence = ({
514
659
  unitSpan
515
660
  ] }),
516
661
  " ",
517
- /* @__PURE__ */ jsxs8(
518
- "select",
662
+ /* @__PURE__ */ jsx8(
663
+ Picker,
519
664
  {
520
- "aria-label": "Low end",
665
+ label: "Low end",
521
666
  title: OP_TITLES[lowOf(rule)],
522
- className: WORD,
523
667
  value: lowOf(rule),
524
- onChange: (event) => onChange(rangeEnd(rule, event.target.value, (op) => ({ lowOp: op }))),
525
- children: [
526
- /* @__PURE__ */ jsx9("optgroup", { label: "Range", children: RANGE_LOWS.map((op) => /* @__PURE__ */ jsx9("option", { value: op, children: LOW_SYMBOLS[op] }, op)) }),
527
- /* @__PURE__ */ jsx9(OneSidedOptions, {})
528
- ]
668
+ choices: RANGE_LOW,
669
+ onChange: (value) => onChange(rangeEnd(rule, value, (op) => ({ lowOp: op })))
529
670
  }
530
671
  ),
531
672
  " "
532
673
  ] }) : null,
533
- /* @__PURE__ */ jsx9(
534
- "select",
674
+ /* @__PURE__ */ jsx8(
675
+ Picker,
535
676
  {
536
- "aria-label": "Measure",
677
+ label: "Measure",
537
678
  title: info?.hint,
538
- className: WORD,
539
679
  value: rule.metric,
540
- onChange: (event) => {
541
- if (event.target.value === MORE) showMore(event.currentTarget, setAllMetrics);
542
- else onChange(withMetric(rule, event.target.value, units));
543
- },
544
- autoFocus,
545
- children: /* @__PURE__ */ jsx9(MetricOptions, { subject: rule.subject, current: rule.metric, all: allMetrics })
680
+ choices: metricChoices(rule.subject, rule.metric, allMetrics),
681
+ onChange: (value) => onChange(withMetric(rule, value, units)),
682
+ onMore: () => setAllMetrics(true),
683
+ autoFocus
546
684
  }
547
685
  ),
548
- info && !flag ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
686
+ info && !flag ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
549
687
  " ",
550
- /* @__PURE__ */ jsx9(Comparison, { rule, ranged, onChange }),
688
+ /* @__PURE__ */ jsx8(Comparison, { rule, ranged, onChange }),
551
689
  " ",
552
- /* @__PURE__ */ jsxs8("span", { className: "whitespace-nowrap", children: [
553
- /* @__PURE__ */ jsx9(
690
+ /* @__PURE__ */ jsxs6("span", { className: "whitespace-nowrap", children: [
691
+ /* @__PURE__ */ jsx8(
554
692
  ValueField,
555
693
  {
556
694
  label: ranged ? "Up to" : "Value",
@@ -569,7 +707,7 @@ var RuleSentence = ({
569
707
 
570
708
  // src/rule-list/swatch.tsx
571
709
  import { cn as cn6 } from "@toolpath/ui";
572
- import { jsx as jsx10 } from "react/jsx-runtime";
710
+ import { jsx as jsx9 } from "react/jsx-runtime";
573
711
  var swatchKeys = listKeys("swatches", "horizontal");
574
712
  var Swatch = ({
575
713
  color,
@@ -581,7 +719,7 @@ var Swatch = ({
581
719
  ref
582
720
  }) => {
583
721
  const dot = cn6("size-4 shrink-0 rounded-full ring-1 ring-black/10 dark:ring-white/15", className);
584
- return onClick ? /* @__PURE__ */ jsx10(
722
+ return onClick ? /* @__PURE__ */ jsx9(
585
723
  "button",
586
724
  {
587
725
  ref,
@@ -597,12 +735,12 @@ var Swatch = ({
597
735
  ),
598
736
  style: { backgroundColor: color }
599
737
  }
600
- ) : /* @__PURE__ */ jsx10("span", { "aria-hidden": true, className: dot, style: { backgroundColor: color } });
738
+ ) : /* @__PURE__ */ jsx9("span", { "aria-hidden": true, className: dot, style: { backgroundColor: color } });
601
739
  };
602
740
  var ColorPicker = ({
603
741
  value,
604
742
  onChange
605
- }) => /* @__PURE__ */ jsx10("div", { className: "flex flex-wrap items-center gap-1.5 py-1", onKeyDown: swatchKeys, children: RULE_COLORS.map((each) => /* @__PURE__ */ jsx10(
743
+ }) => /* @__PURE__ */ jsx9("div", { className: "flex flex-wrap items-center gap-1.5 py-1", onKeyDown: swatchKeys, children: RULE_COLORS.map((each) => /* @__PURE__ */ jsx9(
606
744
  "button",
607
745
  {
608
746
  type: "button",
@@ -622,7 +760,7 @@ var ColorPicker = ({
622
760
  )) });
623
761
 
624
762
  // src/rule-list/rule-row.tsx
625
- import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
763
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
626
764
  var isControl = (target) => target instanceof Element && target.closest("button, select, input, a") !== null;
627
765
  var RuleRow = ({
628
766
  rule,
@@ -639,8 +777,8 @@ var RuleRow = ({
639
777
  units,
640
778
  ...list
641
779
  }) => {
642
- const [picking, setPicking] = useState5(false);
643
- const swatchRef = useRef2(null);
780
+ const [picking, setPicking] = useState6(false);
781
+ const swatchRef = useRef3(null);
644
782
  const count = result?.tags.length ?? 0;
645
783
  const others = result?.otherTags.length ?? 0;
646
784
  const canOpen = count + others > 0;
@@ -655,7 +793,7 @@ var RuleRow = ({
655
793
  onToggle();
656
794
  }
657
795
  };
658
- return /* @__PURE__ */ jsxs9(
796
+ return /* @__PURE__ */ jsxs7(
659
797
  "li",
660
798
  {
661
799
  className: cn7(
@@ -664,7 +802,7 @@ var RuleRow = ({
664
802
  !checking && count === 0 && "opacity-55 transition-opacity focus-within:opacity-100 hover:opacity-100"
665
803
  ),
666
804
  children: [
667
- /* @__PURE__ */ jsxs9(
805
+ /* @__PURE__ */ jsxs7(
668
806
  "div",
669
807
  {
670
808
  onClick: (event) => {
@@ -675,7 +813,7 @@ var RuleRow = ({
675
813
  canOpen && "cursor-pointer"
676
814
  ),
677
815
  children: [
678
- /* @__PURE__ */ jsx11(
816
+ /* @__PURE__ */ jsx10(
679
817
  "button",
680
818
  {
681
819
  type: "button",
@@ -687,7 +825,7 @@ var RuleRow = ({
687
825
  "flex size-7 shrink-0 items-center justify-center rounded text-gray-400 dark:text-zinc-400",
688
826
  canOpen ? "cursor-pointer hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-zinc-700 dark:hover:text-zinc-100" : "invisible"
689
827
  ),
690
- children: /* @__PURE__ */ jsx11(
828
+ children: /* @__PURE__ */ jsx10(
691
829
  CaretRightIcon2,
692
830
  {
693
831
  weight: "bold",
@@ -696,7 +834,7 @@ var RuleRow = ({
696
834
  )
697
835
  }
698
836
  ),
699
- /* @__PURE__ */ jsx11(
837
+ /* @__PURE__ */ jsx10(
700
838
  Swatch,
701
839
  {
702
840
  ref: swatchRef,
@@ -708,9 +846,9 @@ var RuleRow = ({
708
846
  className: "mt-1.5"
709
847
  }
710
848
  ),
711
- /* @__PURE__ */ jsx11(RuleSentence, { rule, units, onChange, autoFocus: focusOnMount }),
712
- /* @__PURE__ */ jsxs9("div", { className: "relative mt-1 shrink-0", children: [
713
- /* @__PURE__ */ jsx11(
849
+ /* @__PURE__ */ jsx10(RuleSentence, { rule, units, onChange, autoFocus: focusOnMount }),
850
+ /* @__PURE__ */ jsxs7("div", { className: "relative mt-1 shrink-0", children: [
851
+ /* @__PURE__ */ jsx10(
714
852
  IconButton,
715
853
  {
716
854
  "aria-label": "Remove rule",
@@ -723,10 +861,10 @@ var RuleRow = ({
723
861
  "group-focus-within/rule:pointer-events-auto group-focus-within/rule:opacity-100",
724
862
  "group-hover/rule:pointer-events-auto group-hover/rule:opacity-100"
725
863
  ),
726
- children: /* @__PURE__ */ jsx11(TrashIcon, { weight: "bold" })
864
+ children: /* @__PURE__ */ jsx10(TrashIcon, { weight: "bold" })
727
865
  }
728
866
  ),
729
- /* @__PURE__ */ jsx11(
867
+ /* @__PURE__ */ jsx10(
730
868
  CountPill,
731
869
  {
732
870
  count,
@@ -741,7 +879,7 @@ var RuleRow = ({
741
879
  ]
742
880
  }
743
881
  ),
744
- picking ? /* @__PURE__ */ jsx11("div", { className: "pb-1 pl-[34px]", children: /* @__PURE__ */ jsx11(
882
+ picking ? /* @__PURE__ */ jsx10("div", { className: "pb-1 pl-[34px]", children: /* @__PURE__ */ jsx10(
745
883
  ColorPicker,
746
884
  {
747
885
  value: rule.color,
@@ -752,7 +890,7 @@ var RuleRow = ({
752
890
  }
753
891
  }
754
892
  ) }) : null,
755
- open && result && check && features && sheets ? /* @__PURE__ */ jsx11(
893
+ open && result && check && features && sheets ? /* @__PURE__ */ jsx10(
756
894
  FeatureRows,
757
895
  {
758
896
  rule,
@@ -777,7 +915,7 @@ var CountPill = ({
777
915
  open,
778
916
  canOpen,
779
917
  onToggle
780
- }) => /* @__PURE__ */ jsx11(
918
+ }) => /* @__PURE__ */ jsx10(
781
919
  "button",
782
920
  {
783
921
  type: "button",
@@ -801,15 +939,15 @@ var countLabel = (count, others) => {
801
939
  };
802
940
 
803
941
  // src/rule-list/rule-set-actions.tsx
804
- import { useRef as useRef3 } from "react";
805
- import { CheckIcon, CopyIcon, DownloadSimpleIcon, UploadSimpleIcon } from "@phosphor-icons/react";
942
+ import { useRef as useRef4 } from "react";
943
+ import { CheckIcon as CheckIcon2, CopyIcon, DownloadSimpleIcon, UploadSimpleIcon } from "@phosphor-icons/react";
806
944
  import { Button, cn as cn8 } from "@toolpath/ui";
807
945
 
808
946
  // src/rule-list/use-timed-value.ts
809
- import { useCallback, useEffect as useEffect2, useState as useState6 } from "react";
947
+ import { useCallback, useEffect as useEffect3, useState as useState7 } from "react";
810
948
  var useTimedValue = (ms) => {
811
- const [box, setBox] = useState6(null);
812
- useEffect2(() => {
949
+ const [box, setBox] = useState7(null);
950
+ useEffect3(() => {
813
951
  if (box === null) return;
814
952
  const timer = setTimeout(() => setBox(null), ms);
815
953
  return () => clearTimeout(timer);
@@ -819,14 +957,17 @@ var useTimedValue = (ms) => {
819
957
  };
820
958
 
821
959
  // src/rule-list/rule-set-actions.tsx
822
- import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
960
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
823
961
  var NOTE_MS = 4e3;
824
962
  var COPIED_MS = 2e3;
825
963
  var EXPORT_FILE_NAME = "dfm-rules.json";
826
- var RuleSetActions = ({ rules }) => {
964
+ var RuleSetActions = ({
965
+ rules,
966
+ actions
967
+ }) => {
827
968
  const [note, setNote] = useTimedValue(NOTE_MS);
828
969
  const [copied, setCopied] = useTimedValue(COPIED_MS);
829
- const fileInput = useRef3(null);
970
+ const fileInput = useRef4(null);
830
971
  const importRules = async (file) => {
831
972
  if (!file) return;
832
973
  let text;
@@ -854,23 +995,24 @@ var RuleSetActions = ({ rules }) => {
854
995
  setCopied(true);
855
996
  setNote("Paste the prompt into an LLM, then import the JSON it writes.");
856
997
  };
857
- return /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-2 px-4 pt-2 pb-3", children: [
858
- /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap items-center gap-2", children: [
859
- /* @__PURE__ */ jsx12(Button, { variant: "secondary", onClick: () => fileInput.current?.click(), children: /* @__PURE__ */ jsx12(ActionLabel, { icon: /* @__PURE__ */ jsx12(UploadSimpleIcon, { weight: "bold" }), children: "Import\u2026" }) }),
860
- /* @__PURE__ */ jsx12(Button, { variant: "secondary", onClick: () => downloadRuleSet(rules), children: /* @__PURE__ */ jsx12(ActionLabel, { icon: /* @__PURE__ */ jsx12(DownloadSimpleIcon, { weight: "bold" }), children: "Export" }) }),
861
- /* @__PURE__ */ jsx12(
998
+ return /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-2 px-4 pt-2 pb-3", children: [
999
+ /* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap items-center gap-2", children: [
1000
+ /* @__PURE__ */ jsx11(Button, { variant: "secondary", onClick: () => fileInput.current?.click(), children: /* @__PURE__ */ jsx11(ActionLabel, { icon: /* @__PURE__ */ jsx11(UploadSimpleIcon, { weight: "bold" }), children: "Import\u2026" }) }),
1001
+ /* @__PURE__ */ jsx11(Button, { variant: "secondary", onClick: () => downloadRuleSet(rules), children: /* @__PURE__ */ jsx11(ActionLabel, { icon: /* @__PURE__ */ jsx11(DownloadSimpleIcon, { weight: "bold" }), children: "Export" }) }),
1002
+ /* @__PURE__ */ jsx11(
862
1003
  Button,
863
1004
  {
864
1005
  variant: "secondary",
865
1006
  title: "Copies a prompt that walks an LLM through writing a rule set for you",
866
1007
  onClick: () => void copyPrompt(),
867
- children: /* @__PURE__ */ jsxs10("span", { className: "grid justify-items-center *:col-start-1 *:row-start-1", children: [
868
- /* @__PURE__ */ jsx12(ActionLabel, { hidden: copied !== null, icon: /* @__PURE__ */ jsx12(CopyIcon, { weight: "bold" }), children: "Copy LLM prompt" }),
869
- /* @__PURE__ */ jsx12(ActionLabel, { hidden: copied === null, icon: /* @__PURE__ */ jsx12(CheckIcon, { weight: "bold" }), children: "Copied" })
1008
+ children: /* @__PURE__ */ jsxs8("span", { className: "grid justify-items-center *:col-start-1 *:row-start-1", children: [
1009
+ /* @__PURE__ */ jsx11(ActionLabel, { hidden: copied !== null, icon: /* @__PURE__ */ jsx11(CopyIcon, { weight: "bold" }), children: "Copy LLM prompt" }),
1010
+ /* @__PURE__ */ jsx11(ActionLabel, { hidden: copied === null, icon: /* @__PURE__ */ jsx11(CheckIcon2, { weight: "bold" }), children: "Copied" })
870
1011
  ] })
871
1012
  }
872
1013
  ),
873
- /* @__PURE__ */ jsx12(
1014
+ actions,
1015
+ /* @__PURE__ */ jsx11(
874
1016
  "input",
875
1017
  {
876
1018
  ref: fileInput,
@@ -885,10 +1027,10 @@ var RuleSetActions = ({ rules }) => {
885
1027
  }
886
1028
  )
887
1029
  ] }),
888
- note ? /* @__PURE__ */ jsx12("p", { className: "text-xs text-gray-500 dark:text-zinc-400", role: "status", children: note }) : null
1030
+ note ? /* @__PURE__ */ jsx11("p", { className: "text-xs text-gray-500 dark:text-zinc-400", role: "status", children: note }) : null
889
1031
  ] });
890
1032
  };
891
- var ActionLabel = ({ icon, children, hidden = false }) => /* @__PURE__ */ jsxs10(
1033
+ var ActionLabel = ({ icon, children, hidden = false }) => /* @__PURE__ */ jsxs8(
892
1034
  "span",
893
1035
  {
894
1036
  "aria-hidden": hidden || void 0,
@@ -917,7 +1059,7 @@ var importedNote = (count, skipped, name) => {
917
1059
  };
918
1060
 
919
1061
  // src/rule-list/rule-list.tsx
920
- import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1062
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
921
1063
  var ruleListKeys = listKeys("rules");
922
1064
  var RuleList = ({
923
1065
  rules,
@@ -925,10 +1067,11 @@ var RuleList = ({
925
1067
  features,
926
1068
  sheets,
927
1069
  units,
1070
+ actions,
928
1071
  ...list
929
1072
  }) => {
930
- const [openIds, setOpenIds] = useState7(/* @__PURE__ */ new Set());
931
- const [added, setAdded] = useState7(null);
1073
+ const [openIds, setOpenIds] = useState8(/* @__PURE__ */ new Set());
1074
+ const [added, setAdded] = useState8(null);
932
1075
  const toggleOpen = (id) => setOpenIds((was) => {
933
1076
  const next = new Set(was);
934
1077
  if (!next.delete(id)) next.add(id);
@@ -944,7 +1087,7 @@ var RuleList = ({
944
1087
  };
945
1088
  const active = rules.rules.filter(found);
946
1089
  const none = rules.rules.filter((rule) => !found(rule));
947
- const row = (rule) => /* @__PURE__ */ jsx13(
1090
+ const row = (rule) => /* @__PURE__ */ jsx12(
948
1091
  RuleRow,
949
1092
  {
950
1093
  rule,
@@ -965,11 +1108,11 @@ var RuleList = ({
965
1108
  );
966
1109
  return (
967
1110
  // Grows with its rules, and scrolls once the panel can grow no more.
968
- /* @__PURE__ */ jsxs11(ScrollArea, { className: "min-h-0 flex-auto", children: [
969
- /* @__PURE__ */ jsxs11("ul", { className: "flex flex-col gap-0.5 pt-2 pr-4 pl-2", children: [
1111
+ /* @__PURE__ */ jsxs9(ScrollArea, { className: "min-h-0 flex-auto", children: [
1112
+ /* @__PURE__ */ jsxs9("ul", { className: "flex flex-col gap-0.5 pt-2 pr-4 pl-2", children: [
970
1113
  active.map(row),
971
1114
  none.map(row),
972
- /* @__PURE__ */ jsx13(
1115
+ /* @__PURE__ */ jsx12(
973
1116
  NewRule,
974
1117
  {
975
1118
  onAdd: (subject) => {
@@ -980,7 +1123,7 @@ var RuleList = ({
980
1123
  }
981
1124
  )
982
1125
  ] }),
983
- /* @__PURE__ */ jsx13(RuleSetActions, { rules })
1126
+ /* @__PURE__ */ jsx12(RuleSetActions, { rules, actions })
984
1127
  ] })
985
1128
  );
986
1129
  };
@@ -989,7 +1132,7 @@ var RuleList = ({
989
1132
  import { useMemo as useMemo4 } from "react";
990
1133
 
991
1134
  // src/reach/reach-section.tsx
992
- import { useId, useMemo as useMemo3, useState as useState8 } from "react";
1135
+ import { useId as useId2, useMemo as useMemo3, useState as useState9 } from "react";
993
1136
 
994
1137
  // src/reach/reading.tsx
995
1138
  import { heightAt } from "@toolpath/tool-support";
@@ -1064,7 +1207,7 @@ var READ = "stroke-sky-600 dark:stroke-sky-400";
1064
1207
  var READ_DOT = "fill-sky-600 dark:fill-sky-400";
1065
1208
 
1066
1209
  // src/reach/reading.tsx
1067
- import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1210
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1068
1211
  var readingAt = ({ offsets, heights }, offset) => ({
1069
1212
  offset,
1070
1213
  height: heightAt({ horizontalOffset: offsets, verticalOffset: heights }, offset),
@@ -1087,8 +1230,8 @@ var ReadingMark = ({
1087
1230
  const pointerX = x(reading.offset);
1088
1231
  const onRight = pointerX + READ_INSET + textWidth <= width - READ_INSET;
1089
1232
  const labelX = onRight ? pointerX + READ_INSET : Math.min(Math.max(pointerX - READ_INSET, READ_INSET + textWidth), width - READ_INSET);
1090
- return /* @__PURE__ */ jsxs12("g", { pointerEvents: "none", children: [
1091
- /* @__PURE__ */ jsx14(
1233
+ return /* @__PURE__ */ jsxs10("g", { pointerEvents: "none", children: [
1234
+ /* @__PURE__ */ jsx13(
1092
1235
  "line",
1093
1236
  {
1094
1237
  x1: pointerX,
@@ -1100,8 +1243,8 @@ var ReadingMark = ({
1100
1243
  className: READ
1101
1244
  }
1102
1245
  ),
1103
- /* @__PURE__ */ jsx14("circle", { cx: pointerX, cy: y(reading.height), r: 4, className: READ_DOT }),
1104
- /* @__PURE__ */ jsx14(
1246
+ /* @__PURE__ */ jsx13("circle", { cx: pointerX, cy: y(reading.height), r: 4, className: READ_DOT }),
1247
+ /* @__PURE__ */ jsx13(
1105
1248
  "text",
1106
1249
  {
1107
1250
  x: labelX,
@@ -1200,8 +1343,8 @@ var sectionGeometry = (whole, feature, width) => {
1200
1343
  };
1201
1344
 
1202
1345
  // src/reach/section-parts.tsx
1203
- import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
1204
- var Arrows = ({ id }) => /* @__PURE__ */ jsx15("defs", { children: /* @__PURE__ */ jsx15(
1346
+ import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1347
+ var Arrows = ({ id }) => /* @__PURE__ */ jsx14("defs", { children: /* @__PURE__ */ jsx14(
1205
1348
  "marker",
1206
1349
  {
1207
1350
  id,
@@ -1211,14 +1354,14 @@ var Arrows = ({ id }) => /* @__PURE__ */ jsx15("defs", { children: /* @__PURE__
1211
1354
  markerWidth: "7",
1212
1355
  markerHeight: "7",
1213
1356
  orient: "auto-start-reverse",
1214
- children: /* @__PURE__ */ jsx15("path", { d: "M 0 1.5 L 10 5 L 0 8.5 z", className: ARROW })
1357
+ children: /* @__PURE__ */ jsx14("path", { d: "M 0 1.5 L 10 5 L 0 8.5 z", className: ARROW })
1215
1358
  }
1216
1359
  ) });
1217
1360
  var SectionShapes = ({ geometry }) => {
1218
1361
  const { x, floorY, right, featureTopY, featureBottomY, wallTop, feature } = geometry;
1219
1362
  const floorLeft = feature.through ? x(0) : SECTION_PAD.left;
1220
- return /* @__PURE__ */ jsxs13(Fragment4, { children: [
1221
- /* @__PURE__ */ jsx15(
1363
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1364
+ /* @__PURE__ */ jsx14(
1222
1365
  "rect",
1223
1366
  {
1224
1367
  x: SECTION_PAD.left,
@@ -1228,7 +1371,7 @@ var SectionShapes = ({ geometry }) => {
1228
1371
  className: FEATURE_FILL
1229
1372
  }
1230
1373
  ),
1231
- /* @__PURE__ */ jsx15(
1374
+ /* @__PURE__ */ jsx14(
1232
1375
  "rect",
1233
1376
  {
1234
1377
  x: floorLeft,
@@ -1238,9 +1381,9 @@ var SectionShapes = ({ geometry }) => {
1238
1381
  className: FLOOR
1239
1382
  }
1240
1383
  ),
1241
- /* @__PURE__ */ jsx15("path", { d: geometry.materialPath, strokeWidth: 1, className: MATERIAL }),
1242
- /* @__PURE__ */ jsx15("line", { x1: floorLeft, x2: right, y1: floorY, y2: floorY, strokeWidth: 1, className: LINE }),
1243
- feature.through ? null : /* @__PURE__ */ jsx15(
1384
+ /* @__PURE__ */ jsx14("path", { d: geometry.materialPath, strokeWidth: 1, className: MATERIAL }),
1385
+ /* @__PURE__ */ jsx14("line", { x1: floorLeft, x2: right, y1: floorY, y2: floorY, strokeWidth: 1, className: LINE }),
1386
+ feature.through ? null : /* @__PURE__ */ jsx14(
1244
1387
  "line",
1245
1388
  {
1246
1389
  x1: SECTION_PAD.left,
@@ -1251,7 +1394,7 @@ var SectionShapes = ({ geometry }) => {
1251
1394
  className: FEATURE_LINE
1252
1395
  }
1253
1396
  ),
1254
- /* @__PURE__ */ jsx15(
1397
+ /* @__PURE__ */ jsx14(
1255
1398
  "line",
1256
1399
  {
1257
1400
  x1: x(0),
@@ -1262,7 +1405,7 @@ var SectionShapes = ({ geometry }) => {
1262
1405
  className: FEATURE_LINE
1263
1406
  }
1264
1407
  ),
1265
- /* @__PURE__ */ jsx15(
1408
+ /* @__PURE__ */ jsx14(
1266
1409
  "line",
1267
1410
  {
1268
1411
  x1: x(0),
@@ -1278,8 +1421,8 @@ var SectionShapes = ({ geometry }) => {
1278
1421
  };
1279
1422
  var SectionHeadings = ({ geometry }) => {
1280
1423
  const { right } = geometry;
1281
- return /* @__PURE__ */ jsxs13(Fragment4, { children: [
1282
- /* @__PURE__ */ jsx15(
1424
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1425
+ /* @__PURE__ */ jsx14(
1283
1426
  "text",
1284
1427
  {
1285
1428
  x: geometry.featureHeadingX,
@@ -1291,7 +1434,7 @@ var SectionHeadings = ({ geometry }) => {
1291
1434
  children: geometry.featureHeading
1292
1435
  }
1293
1436
  ),
1294
- /* @__PURE__ */ jsx15(
1437
+ /* @__PURE__ */ jsx14(
1295
1438
  "text",
1296
1439
  {
1297
1440
  x: right,
@@ -1305,7 +1448,7 @@ var SectionHeadings = ({ geometry }) => {
1305
1448
  )
1306
1449
  ] });
1307
1450
  };
1308
- var FeatureBreak = ({ geometry }) => geometry.featureCut ? /* @__PURE__ */ jsx15(
1451
+ var FeatureBreak = ({ geometry }) => geometry.featureCut ? /* @__PURE__ */ jsx14(
1309
1452
  "path",
1310
1453
  {
1311
1454
  d: featureBreak(SECTION_PAD.left, geometry.featureTopY, geometry.featureBottomY),
@@ -1318,9 +1461,9 @@ var FeatureDepth = ({ geometry, units }) => {
1318
1461
  const { x, featureTopY, datumX } = geometry;
1319
1462
  const { depth } = geometry.feature;
1320
1463
  if (depth === null) return null;
1321
- return /* @__PURE__ */ jsxs13("g", { children: [
1322
- /* @__PURE__ */ jsx15("title", { children: "Feature depth" }),
1323
- /* @__PURE__ */ jsx15(
1464
+ return /* @__PURE__ */ jsxs11("g", { children: [
1465
+ /* @__PURE__ */ jsx14("title", { children: "Feature depth" }),
1466
+ /* @__PURE__ */ jsx14(
1324
1467
  "line",
1325
1468
  {
1326
1469
  x1: SECTION_PAD.left,
@@ -1331,7 +1474,7 @@ var FeatureDepth = ({ geometry, units }) => {
1331
1474
  className: FEATURE_LINE
1332
1475
  }
1333
1476
  ),
1334
- /* @__PURE__ */ jsx15(
1477
+ /* @__PURE__ */ jsx14(
1335
1478
  "line",
1336
1479
  {
1337
1480
  x1: x(0),
@@ -1343,7 +1486,7 @@ var FeatureDepth = ({ geometry, units }) => {
1343
1486
  className: FEATURE_LINE
1344
1487
  }
1345
1488
  ),
1346
- x(0) - SECTION_PAD.left >= DEPTH_WORD_PX ? /* @__PURE__ */ jsx15(
1489
+ x(0) - SECTION_PAD.left >= DEPTH_WORD_PX ? /* @__PURE__ */ jsx14(
1347
1490
  "text",
1348
1491
  {
1349
1492
  x: SECTION_PAD.left + 4,
@@ -1354,7 +1497,7 @@ var FeatureDepth = ({ geometry, units }) => {
1354
1497
  children: "depth"
1355
1498
  }
1356
1499
  ) : null,
1357
- /* @__PURE__ */ jsx15(
1500
+ /* @__PURE__ */ jsx14(
1358
1501
  "text",
1359
1502
  {
1360
1503
  x: datumX + 4,
@@ -1370,8 +1513,8 @@ var FeatureDepth = ({ geometry, units }) => {
1370
1513
  var ReadRun = ({ geometry, units, arrowId }) => {
1371
1514
  const { x, last, cut, floorY, ordinateY } = geometry;
1372
1515
  if (last <= 0 || cut) return null;
1373
- return /* @__PURE__ */ jsxs13("g", { children: [
1374
- /* @__PURE__ */ jsx15(
1516
+ return /* @__PURE__ */ jsxs11("g", { children: [
1517
+ /* @__PURE__ */ jsx14(
1375
1518
  "line",
1376
1519
  {
1377
1520
  x1: x(0),
@@ -1384,7 +1527,7 @@ var ReadRun = ({ geometry, units, arrowId }) => {
1384
1527
  className: LINE
1385
1528
  }
1386
1529
  ),
1387
- /* @__PURE__ */ jsx15(
1530
+ /* @__PURE__ */ jsx14(
1388
1531
  "line",
1389
1532
  {
1390
1533
  x1: x(last),
@@ -1395,7 +1538,7 @@ var ReadRun = ({ geometry, units, arrowId }) => {
1395
1538
  className: LINE
1396
1539
  }
1397
1540
  ),
1398
- /* @__PURE__ */ jsxs13(
1541
+ /* @__PURE__ */ jsxs11(
1399
1542
  "text",
1400
1543
  {
1401
1544
  x: (x(0) + x(last)) / 2,
@@ -1414,8 +1557,8 @@ var ReadRun = ({ geometry, units, arrowId }) => {
1414
1557
  var OffsetOrdinates = (props) => {
1415
1558
  const { geometry, units } = props;
1416
1559
  const { x, y, right, width, floorY, ordinateY } = geometry;
1417
- return /* @__PURE__ */ jsxs13(Fragment4, { children: [
1418
- /* @__PURE__ */ jsx15(
1560
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1561
+ /* @__PURE__ */ jsx14(
1419
1562
  "text",
1420
1563
  {
1421
1564
  x: Math.min(
@@ -1430,7 +1573,7 @@ var OffsetOrdinates = (props) => {
1430
1573
  children: "XY PLANE DISTANCE FROM FEATURE"
1431
1574
  }
1432
1575
  ),
1433
- /* @__PURE__ */ jsx15(
1576
+ /* @__PURE__ */ jsx14(
1434
1577
  "line",
1435
1578
  {
1436
1579
  x1: x(0),
@@ -1441,9 +1584,9 @@ var OffsetOrdinates = (props) => {
1441
1584
  className: LINE
1442
1585
  }
1443
1586
  ),
1444
- /* @__PURE__ */ jsx15("text", { x: x(0), y: ordinateY + 15, textAnchor: "middle", fontSize: 10, className: TEXT, children: "0" }),
1445
- geometry.across.map((riser) => /* @__PURE__ */ jsxs13("g", { children: [
1446
- /* @__PURE__ */ jsx15(
1587
+ /* @__PURE__ */ jsx14("text", { x: x(0), y: ordinateY + 15, textAnchor: "middle", fontSize: 10, className: TEXT, children: "0" }),
1588
+ geometry.across.map((riser) => /* @__PURE__ */ jsxs11("g", { children: [
1589
+ /* @__PURE__ */ jsx14(
1447
1590
  "line",
1448
1591
  {
1449
1592
  x1: x(riser.at),
@@ -1455,7 +1598,7 @@ var OffsetOrdinates = (props) => {
1455
1598
  className: LINE
1456
1599
  }
1457
1600
  ),
1458
- /* @__PURE__ */ jsx15(
1601
+ /* @__PURE__ */ jsx14(
1459
1602
  "text",
1460
1603
  {
1461
1604
  x: x(riser.at),
@@ -1467,15 +1610,15 @@ var OffsetOrdinates = (props) => {
1467
1610
  }
1468
1611
  )
1469
1612
  ] }, `x:${riser.at}`)),
1470
- /* @__PURE__ */ jsx15(ReadRun, { ...props })
1613
+ /* @__PURE__ */ jsx14(ReadRun, { ...props })
1471
1614
  ] });
1472
1615
  };
1473
1616
  var HeightOrdinates = ({ geometry, units }) => {
1474
1617
  const { y, right, floorY, datumX } = geometry;
1475
- return /* @__PURE__ */ jsxs13(Fragment4, { children: [
1476
- /* @__PURE__ */ jsx15("text", { x: datumX + 4, y: floorY + 3.5, fontSize: 10, className: TEXT, children: "0" }),
1477
- geometry.up.map((h) => /* @__PURE__ */ jsxs13("g", { children: [
1478
- /* @__PURE__ */ jsx15(
1618
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1619
+ /* @__PURE__ */ jsx14("text", { x: datumX + 4, y: floorY + 3.5, fontSize: 10, className: TEXT, children: "0" }),
1620
+ geometry.up.map((h) => /* @__PURE__ */ jsxs11("g", { children: [
1621
+ /* @__PURE__ */ jsx14(
1479
1622
  "line",
1480
1623
  {
1481
1624
  x1: right - 2,
@@ -1487,37 +1630,37 @@ var HeightOrdinates = ({ geometry, units }) => {
1487
1630
  className: LINE
1488
1631
  }
1489
1632
  ),
1490
- /* @__PURE__ */ jsx15("text", { x: datumX + 4, y: y(h) + 3.5, fontSize: 10, className: TEXT, children: lengthValue(h, units) })
1633
+ /* @__PURE__ */ jsx14("text", { x: datumX + 4, y: y(h) + 3.5, fontSize: 10, className: TEXT, children: lengthValue(h, units) })
1491
1634
  ] }, `y:${h}`))
1492
1635
  ] });
1493
1636
  };
1494
1637
 
1495
1638
  // src/reach/reach-section.tsx
1496
- import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
1639
+ import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
1497
1640
  var ReachSection = ({
1498
1641
  curve: whole,
1499
1642
  units,
1500
1643
  feature,
1501
1644
  width
1502
1645
  }) => {
1503
- const [hover, setHover] = useState8(null);
1646
+ const [hover, setHover] = useState9(null);
1504
1647
  const geometry = useMemo3(() => sectionGeometry(whole, feature, width), [whole, feature, width]);
1505
- const reactId = useId();
1648
+ const reactId = useId2();
1506
1649
  const arrowId = `reach-dim-arrow-${reactId.replace(/[^\w-]/g, "")}`;
1507
1650
  const reading = hover === null ? null : readingAt(whole, hover);
1508
1651
  const drawing = useMemo3(
1509
- () => /* @__PURE__ */ jsxs14(Fragment5, { children: [
1510
- /* @__PURE__ */ jsx16(Arrows, { id: arrowId }),
1511
- /* @__PURE__ */ jsx16(SectionShapes, { geometry }),
1512
- /* @__PURE__ */ jsx16(SectionHeadings, { geometry }),
1513
- /* @__PURE__ */ jsx16(FeatureBreak, { geometry }),
1514
- /* @__PURE__ */ jsx16(FeatureDepth, { geometry, units }),
1515
- /* @__PURE__ */ jsx16(OffsetOrdinates, { geometry, units, arrowId }),
1516
- /* @__PURE__ */ jsx16(HeightOrdinates, { geometry, units })
1652
+ () => /* @__PURE__ */ jsxs12(Fragment5, { children: [
1653
+ /* @__PURE__ */ jsx15(Arrows, { id: arrowId }),
1654
+ /* @__PURE__ */ jsx15(SectionShapes, { geometry }),
1655
+ /* @__PURE__ */ jsx15(SectionHeadings, { geometry }),
1656
+ /* @__PURE__ */ jsx15(FeatureBreak, { geometry }),
1657
+ /* @__PURE__ */ jsx15(FeatureDepth, { geometry, units }),
1658
+ /* @__PURE__ */ jsx15(OffsetOrdinates, { geometry, units, arrowId }),
1659
+ /* @__PURE__ */ jsx15(HeightOrdinates, { geometry, units })
1517
1660
  ] }),
1518
1661
  [arrowId, geometry, units]
1519
1662
  );
1520
- return /* @__PURE__ */ jsxs14(
1663
+ return /* @__PURE__ */ jsxs12(
1521
1664
  "svg",
1522
1665
  {
1523
1666
  viewBox: `0 0 ${width} ${geometry.height}`,
@@ -1530,17 +1673,17 @@ var ReachSection = ({
1530
1673
  onPointerLeave: () => setHover(null),
1531
1674
  children: [
1532
1675
  drawing,
1533
- reading ? /* @__PURE__ */ jsx16(ReadingMark, { geometry, reading, units }) : null
1676
+ reading ? /* @__PURE__ */ jsx15(ReadingMark, { geometry, reading, units }) : null
1534
1677
  ]
1535
1678
  }
1536
1679
  );
1537
1680
  };
1538
1681
 
1539
1682
  // src/reach/use-element-size.ts
1540
- import { useLayoutEffect, useRef as useRef4, useState as useState9 } from "react";
1683
+ import { useLayoutEffect, useRef as useRef5, useState as useState10 } from "react";
1541
1684
  var useElementSize = () => {
1542
- const ref = useRef4(null);
1543
- const [size, setSize] = useState9({ width: 0, height: 0 });
1685
+ const ref = useRef5(null);
1686
+ const [size, setSize] = useState10({ width: 0, height: 0 });
1544
1687
  useLayoutEffect(() => {
1545
1688
  const element = ref.current;
1546
1689
  if (!element) return;
@@ -1558,12 +1701,12 @@ var useElementSize = () => {
1558
1701
  };
1559
1702
 
1560
1703
  // src/reach/reach-chart.tsx
1561
- import { Fragment as Fragment6, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
1704
+ import { Fragment as Fragment6, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1562
1705
  var CHART_MIN_W = 200;
1563
1706
  var DRAWING_MIN_W = 240;
1564
1707
  var ReachChart = ({ curve, units, feature }) => {
1565
1708
  const [ref, size] = useElementSize();
1566
- return /* @__PURE__ */ jsx17("div", { ref, className: "w-full", children: /* @__PURE__ */ jsx17(
1709
+ return /* @__PURE__ */ jsx16("div", { ref, className: "w-full", children: /* @__PURE__ */ jsx16(
1567
1710
  ReachSection,
1568
1711
  {
1569
1712
  curve,
@@ -1581,7 +1724,7 @@ var CutNote = ({
1581
1724
  if (!trimmed.cut) return null;
1582
1725
  const drawnTo = trimmed.curve.offsets[trimmed.curve.offsets.length - 1] ?? 0;
1583
1726
  const readTo = curve.offsets[curve.offsets.length - 1] ?? 0;
1584
- return /* @__PURE__ */ jsxs15(Fragment6, { children: [
1727
+ return /* @__PURE__ */ jsxs13(Fragment6, { children: [
1585
1728
  " ",
1586
1729
  "It is at full height from ",
1587
1730
  lengthValue(drawnTo, units),
@@ -1596,13 +1739,13 @@ var CutNote = ({
1596
1739
  };
1597
1740
  var ReachDrawing = ({ curve, units, feature }) => {
1598
1741
  const [ref, size] = useElementSize();
1599
- return /* @__PURE__ */ jsxs15("div", { ref, className: "flex flex-col gap-3 p-4", children: [
1600
- /* @__PURE__ */ jsxs15("figure", { className: "flex flex-col gap-1", children: [
1601
- /* @__PURE__ */ jsxs15("figcaption", { className: "text-2xs font-semibold text-gray-400 uppercase dark:text-zinc-500", children: [
1742
+ return /* @__PURE__ */ jsxs13("div", { ref, className: "flex flex-col gap-3 p-4", children: [
1743
+ /* @__PURE__ */ jsxs13("figure", { className: "flex flex-col gap-1", children: [
1744
+ /* @__PURE__ */ jsxs13("figcaption", { className: "text-2xs font-semibold text-gray-400 uppercase dark:text-zinc-500", children: [
1602
1745
  "Section at the wall \xB7 to scale \xB7 ",
1603
1746
  unitLabel(units)
1604
1747
  ] }),
1605
- /* @__PURE__ */ jsx17(
1748
+ /* @__PURE__ */ jsx16(
1606
1749
  ReachSection,
1607
1750
  {
1608
1751
  curve,
@@ -1612,9 +1755,9 @@ var ReachDrawing = ({ curve, units, feature }) => {
1612
1755
  }
1613
1756
  )
1614
1757
  ] }),
1615
- /* @__PURE__ */ jsxs15("p", { className: "text-2xs text-gray-400 dark:text-zinc-500", children: [
1758
+ /* @__PURE__ */ jsxs13("p", { className: "text-2xs text-gray-400 dark:text-zinc-500", children: [
1616
1759
  "Point at the walls to read them: the distance from the feature, then the reach a tool needs there.",
1617
- /* @__PURE__ */ jsx17(CutNote, { curve, units })
1760
+ /* @__PURE__ */ jsx16(CutNote, { curve, units })
1618
1761
  ] })
1619
1762
  ] });
1620
1763
  };
@@ -1638,7 +1781,7 @@ var storageFolds = (storage, prefix) => ({
1638
1781
  });
1639
1782
 
1640
1783
  // src/feature-panel/feature-details.tsx
1641
- import { useCallback as useCallback2, useMemo as useMemo7, useState as useState12 } from "react";
1784
+ import { useCallback as useCallback2, useMemo as useMemo7, useState as useState13 } from "react";
1642
1785
  import { CrosshairIcon, InfoIcon } from "@phosphor-icons/react";
1643
1786
  import { Button as Button3, Tooltip, cn as cn12 } from "@toolpath/ui";
1644
1787
 
@@ -1655,22 +1798,22 @@ var secondaryText = (look) => look.muted === "strong" ? "text-gray dark:text-zin
1655
1798
  var faintText = (look) => look.muted === "strong" ? "text-gray dark:text-zinc-300" : "text-gray-300 dark:text-zinc-500";
1656
1799
 
1657
1800
  // src/feature-panel/feature-name.tsx
1658
- import { Fragment as Fragment7, jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
1659
- var FeatureName = ({ feature, label, look }) => /* @__PURE__ */ jsxs16(Fragment7, { children: [
1801
+ import { Fragment as Fragment7, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1802
+ var FeatureName = ({ feature, label, look }) => /* @__PURE__ */ jsxs14(Fragment7, { children: [
1660
1803
  label ?? featureTypeLabel(feature.featureType),
1661
1804
  " ",
1662
- /* @__PURE__ */ jsxs16("span", { className: cn9("font-normal", faintText(look)), children: [
1805
+ /* @__PURE__ */ jsxs14("span", { className: cn9("font-normal", faintText(look)), children: [
1663
1806
  "from ",
1664
1807
  directionLabel(feature.machiningDirection)
1665
1808
  ] })
1666
1809
  ] });
1667
- var RequiredBadge = () => /* @__PURE__ */ jsx18("span", { className: "rounded-full bg-gray-100 px-1.5 text-2xs font-semibold text-gray-500 uppercase dark:bg-zinc-800 dark:text-zinc-300", children: "Required" });
1810
+ var RequiredBadge = () => /* @__PURE__ */ jsx17("span", { className: "rounded-full bg-gray-100 px-1.5 text-2xs font-semibold text-gray-500 uppercase dark:bg-zinc-800 dark:text-zinc-300", children: "Required" });
1668
1811
 
1669
1812
  // src/feature-panel/fold-section.tsx
1670
- import { useState as useState10 } from "react";
1813
+ import { useState as useState11 } from "react";
1671
1814
  import { CaretRightIcon as CaretRightIcon3 } from "@phosphor-icons/react";
1672
1815
  import { cn as cn10 } from "@toolpath/ui";
1673
- import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
1816
+ import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1674
1817
  var FoldSection = ({
1675
1818
  id,
1676
1819
  title: title2,
@@ -1680,10 +1823,10 @@ var FoldSection = ({
1680
1823
  className,
1681
1824
  children
1682
1825
  }) => {
1683
- const [open, setOpen] = useState10(() => folds?.read(id) ?? defaultOpen);
1684
- return /* @__PURE__ */ jsxs17("section", { className: cn10("flex flex-col gap-1", className), children: [
1685
- /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-1", children: [
1686
- /* @__PURE__ */ jsxs17(
1826
+ const [open, setOpen] = useState11(() => folds?.read(id) ?? defaultOpen);
1827
+ return /* @__PURE__ */ jsxs15("section", { className: cn10("flex flex-col gap-1", className), children: [
1828
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-1", children: [
1829
+ /* @__PURE__ */ jsxs15(
1687
1830
  "button",
1688
1831
  {
1689
1832
  type: "button",
@@ -1698,14 +1841,14 @@ var FoldSection = ({
1698
1841
  "outline-none focus-visible:ring-2 focus-visible:ring-info/75"
1699
1842
  ),
1700
1843
  children: [
1701
- /* @__PURE__ */ jsx19(
1844
+ /* @__PURE__ */ jsx18(
1702
1845
  CaretRightIcon3,
1703
1846
  {
1704
1847
  weight: "bold",
1705
1848
  className: cn10("size-3 shrink-0 transition-transform duration-150", open && "rotate-90")
1706
1849
  }
1707
1850
  ),
1708
- /* @__PURE__ */ jsx19("span", { className: "truncate", children: title2 })
1851
+ /* @__PURE__ */ jsx18("span", { className: "truncate", children: title2 })
1709
1852
  ]
1710
1853
  }
1711
1854
  ),
@@ -1716,22 +1859,22 @@ var FoldSection = ({
1716
1859
  };
1717
1860
 
1718
1861
  // src/feature-panel/read-state.tsx
1719
- import { useEffect as useEffect3, useState as useState11 } from "react";
1862
+ import { useEffect as useEffect4, useState as useState12 } from "react";
1720
1863
  import { Button as Button2 } from "@toolpath/ui";
1721
- import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
1722
- var Reading = ({ children }) => /* @__PURE__ */ jsx20("p", { className: "text-sm text-gray-400 dark:text-zinc-400", role: "status", children });
1864
+ import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
1865
+ var Reading = ({ children }) => /* @__PURE__ */ jsx19("p", { className: "text-sm text-gray-400 dark:text-zinc-400", role: "status", children });
1723
1866
  var ReadFailure = ({
1724
1867
  failure
1725
1868
  }) => {
1726
1869
  const waiting = useWaiting(failure.retryAt);
1727
- return /* @__PURE__ */ jsxs18("div", { className: "flex flex-col items-start gap-2 text-sm", children: [
1728
- /* @__PURE__ */ jsx20("p", { className: "text-danger", role: "alert", children: failure.message }),
1729
- failure.retry ? /* @__PURE__ */ jsx20(Button2, { variant: "secondary", size: "sm", disabled: waiting, onClick: failure.retry, children: "Retry" }) : null
1870
+ return /* @__PURE__ */ jsxs16("div", { className: "flex flex-col items-start gap-2 text-sm", children: [
1871
+ /* @__PURE__ */ jsx19("p", { className: "text-danger", role: "alert", children: failure.message }),
1872
+ failure.retry ? /* @__PURE__ */ jsx19(Button2, { variant: "secondary", size: "sm", disabled: waiting, onClick: failure.retry, children: "Retry" }) : null
1730
1873
  ] });
1731
1874
  };
1732
1875
  var useWaiting = (until) => {
1733
- const [passed, setPassed] = useState11(void 0);
1734
- useEffect3(() => {
1876
+ const [passed, setPassed] = useState12(void 0);
1877
+ useEffect4(() => {
1735
1878
  if (until === void 0) return;
1736
1879
  const timer = setTimeout(() => setPassed(until), Math.max(until - Date.now(), 0));
1737
1880
  return () => clearTimeout(timer);
@@ -1751,7 +1894,7 @@ import { IconButton as IconButton3 } from "@toolpath/ui";
1751
1894
 
1752
1895
  // src/feature-panel/record-sections.tsx
1753
1896
  import { useMemo as useMemo5 } from "react";
1754
- import { CheckIcon as CheckIcon2, CopyIcon as CopyIcon2 } from "@phosphor-icons/react";
1897
+ import { CheckIcon as CheckIcon3, CopyIcon as CopyIcon2 } from "@phosphor-icons/react";
1755
1898
  import { IconButton as IconButton2, cn as cn11 } from "@toolpath/ui";
1756
1899
 
1757
1900
  // src/model/feature-record.ts
@@ -1784,7 +1927,7 @@ var datasheetFields = (datasheet) => {
1784
1927
  };
1785
1928
 
1786
1929
  // src/feature-panel/record-sections.tsx
1787
- import { Fragment as Fragment8, jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
1930
+ import { Fragment as Fragment8, jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
1788
1931
  var COPIED_MS2 = 1500;
1789
1932
  var RecordSections = ({
1790
1933
  record,
@@ -1796,10 +1939,10 @@ var RecordSections = ({
1796
1939
  const { fields, more } = useMemo5(() => datasheetFields(value?.datasheet), [value]);
1797
1940
  const json = useMemo5(() => value ? JSON.stringify(value, null, 2) : "", [value]);
1798
1941
  if (record.status === "ready" && !value) return null;
1799
- const waiting = record.status === "loading" ? /* @__PURE__ */ jsx21(Reading, { children: "Reading the feature\u2019s record\u2026" }) : record.status === "error" ? /* @__PURE__ */ jsx21(ReadFailure, { failure: record }) : null;
1942
+ const waiting = record.status === "loading" ? /* @__PURE__ */ jsx20(Reading, { children: "Reading the feature\u2019s record\u2026" }) : record.status === "error" ? /* @__PURE__ */ jsx20(ReadFailure, { failure: record }) : null;
1800
1943
  const count = more ? `${MAX_DATASHEET_FIELDS}+` : String(fields.length);
1801
- return /* @__PURE__ */ jsxs19(Fragment8, { children: [
1802
- /* @__PURE__ */ jsx21(
1944
+ return /* @__PURE__ */ jsxs17(Fragment8, { children: [
1945
+ /* @__PURE__ */ jsx20(
1803
1946
  FoldSection,
1804
1947
  {
1805
1948
  id: "datasheet",
@@ -1807,37 +1950,37 @@ var RecordSections = ({
1807
1950
  title: value ? `All datasheet fields (${count})` : "All datasheet fields",
1808
1951
  action: value ? popOut?.("datasheet") : null,
1809
1952
  folds,
1810
- children: waiting ?? /* @__PURE__ */ jsx21(DatasheetFields, { record: value, look })
1953
+ children: waiting ?? /* @__PURE__ */ jsx20(DatasheetFields, { record: value, look })
1811
1954
  }
1812
1955
  ),
1813
- /* @__PURE__ */ jsx21(
1956
+ /* @__PURE__ */ jsx20(
1814
1957
  FoldSection,
1815
1958
  {
1816
1959
  id: "raw",
1817
1960
  defaultOpen: false,
1818
1961
  title: "Raw API record",
1819
- action: value ? /* @__PURE__ */ jsxs19(Fragment8, { children: [
1820
- /* @__PURE__ */ jsx21(CopyButton, { text: json }),
1962
+ action: value ? /* @__PURE__ */ jsxs17(Fragment8, { children: [
1963
+ /* @__PURE__ */ jsx20(CopyButton, { text: json }),
1821
1964
  popOut?.("raw")
1822
1965
  ] }) : null,
1823
1966
  folds,
1824
- children: waiting ?? /* @__PURE__ */ jsx21(RawRecord, { json })
1967
+ children: waiting ?? /* @__PURE__ */ jsx20(RawRecord, { json })
1825
1968
  }
1826
1969
  )
1827
1970
  ] });
1828
1971
  };
1829
1972
  var DatasheetFields = ({ record, look, wide = false }) => {
1830
1973
  const { fields } = useMemo5(() => datasheetFields(record?.datasheet), [record]);
1831
- return record?.datasheet ? /* @__PURE__ */ jsx21(
1974
+ return record?.datasheet ? /* @__PURE__ */ jsx20(
1832
1975
  "dl",
1833
1976
  {
1834
1977
  className: cn11(
1835
1978
  "grid grid-cols-[minmax(0,1fr)_auto] gap-y-0.5 text-xs",
1836
1979
  wide ? "gap-x-4 p-4" : "gap-x-3"
1837
1980
  ),
1838
- children: fields.map((field) => /* @__PURE__ */ jsxs19("div", { className: "contents", children: [
1839
- /* @__PURE__ */ jsx21("dt", { className: cn11("truncate font-mono", secondaryText(look)), title: field.path, children: field.path }),
1840
- /* @__PURE__ */ jsx21(
1981
+ children: fields.map((field) => /* @__PURE__ */ jsxs17("div", { className: "contents", children: [
1982
+ /* @__PURE__ */ jsx20("dt", { className: cn11("truncate font-mono", secondaryText(look)), title: field.path, children: field.path }),
1983
+ /* @__PURE__ */ jsx20(
1841
1984
  "dd",
1842
1985
  {
1843
1986
  className: cn11(
@@ -1850,12 +1993,12 @@ var DatasheetFields = ({ record, look, wide = false }) => {
1850
1993
  )
1851
1994
  ] }, field.path))
1852
1995
  }
1853
- ) : /* @__PURE__ */ jsx21("p", { className: cn11("text-sm", secondaryText(look), wide && "p-4"), children: "The API sent no datasheet for this feature." });
1996
+ ) : /* @__PURE__ */ jsx20("p", { className: cn11("text-sm", secondaryText(look), wide && "p-4"), children: "The API sent no datasheet for this feature." });
1854
1997
  };
1855
1998
  var RawRecord = ({
1856
1999
  json,
1857
2000
  wide = false
1858
- }) => /* @__PURE__ */ jsx21(
2001
+ }) => /* @__PURE__ */ jsx20(
1859
2002
  "pre",
1860
2003
  {
1861
2004
  className: cn11(
@@ -1867,7 +2010,7 @@ var RawRecord = ({
1867
2010
  );
1868
2011
  var CopyButton = ({ text }) => {
1869
2012
  const [copied, setCopied] = useTimedValue(COPIED_MS2);
1870
- return /* @__PURE__ */ jsx21(
2013
+ return /* @__PURE__ */ jsx20(
1871
2014
  IconButton2,
1872
2015
  {
1873
2016
  "aria-label": copied ? "Copied" : "Copy raw record",
@@ -1877,13 +2020,13 @@ var CopyButton = ({ text }) => {
1877
2020
  onClick: () => {
1878
2021
  void navigator.clipboard?.writeText(text).then(() => setCopied(true)).catch(() => void 0);
1879
2022
  },
1880
- children: copied ? /* @__PURE__ */ jsx21(CheckIcon2, { weight: "bold" }) : /* @__PURE__ */ jsx21(CopyIcon2, { weight: "bold" })
2023
+ children: copied ? /* @__PURE__ */ jsx20(CheckIcon3, { weight: "bold" }) : /* @__PURE__ */ jsx20(CopyIcon2, { weight: "bold" })
1881
2024
  }
1882
2025
  );
1883
2026
  };
1884
2027
 
1885
2028
  // src/feature-panel/pop-outs.tsx
1886
- import { Fragment as Fragment9, jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
2029
+ import { Fragment as Fragment9, jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
1887
2030
  var TITLES = {
1888
2031
  reach: "Reach",
1889
2032
  datasheet: "All datasheet fields",
@@ -1897,7 +2040,7 @@ var LABELS = {
1897
2040
  var PopOutButton = ({
1898
2041
  id,
1899
2042
  onOpen
1900
- }) => /* @__PURE__ */ jsx22(
2043
+ }) => /* @__PURE__ */ jsx21(
1901
2044
  IconButton3,
1902
2045
  {
1903
2046
  "aria-label": LABELS[id],
@@ -1905,7 +2048,7 @@ var PopOutButton = ({
1905
2048
  variant: "muted",
1906
2049
  size: "sm",
1907
2050
  onClick: () => onOpen(id),
1908
- children: /* @__PURE__ */ jsx22(ArrowSquareOutIcon, { weight: "bold" })
2051
+ children: /* @__PURE__ */ jsx21(ArrowSquareOutIcon, { weight: "bold" })
1909
2052
  }
1910
2053
  );
1911
2054
  var PopOuts = ({
@@ -1921,46 +2064,46 @@ var PopOuts = ({
1921
2064
  const curve = useMemo6(() => readReachCurve(record?.datasheet), [record]);
1922
2065
  const json = useMemo6(() => record ? JSON.stringify(record, null, 2) : "", [record]);
1923
2066
  if (!record) return null;
1924
- return /* @__PURE__ */ jsxs20(Fragment9, { children: [
1925
- open.has("reach") && curve ? /* @__PURE__ */ jsx22(
2067
+ return /* @__PURE__ */ jsxs18(Fragment9, { children: [
2068
+ open.has("reach") && curve ? /* @__PURE__ */ jsx21(
1926
2069
  Window,
1927
2070
  {
1928
2071
  id: "reach",
1929
2072
  title: TITLES.reach,
1930
2073
  subtitle,
1931
- icon: /* @__PURE__ */ jsx22(ChartLineIcon, { weight: "bold" }),
2074
+ icon: /* @__PURE__ */ jsx21(ChartLineIcon, { weight: "bold" }),
1932
2075
  onClose: () => onClose("reach"),
1933
- children: /* @__PURE__ */ jsx22(ReachDrawing, { curve, units, feature: profile })
2076
+ children: /* @__PURE__ */ jsx21(ReachDrawing, { curve, units, feature: profile })
1934
2077
  }
1935
2078
  ) : null,
1936
- open.has("datasheet") ? /* @__PURE__ */ jsx22(
2079
+ open.has("datasheet") ? /* @__PURE__ */ jsx21(
1937
2080
  Window,
1938
2081
  {
1939
2082
  id: "datasheet",
1940
2083
  title: TITLES.datasheet,
1941
2084
  subtitle,
1942
- icon: /* @__PURE__ */ jsx22(TableIcon, { weight: "bold" }),
2085
+ icon: /* @__PURE__ */ jsx21(TableIcon, { weight: "bold" }),
1943
2086
  onClose: () => onClose("datasheet"),
1944
- children: /* @__PURE__ */ jsx22(DatasheetFields, { record, look, wide: true })
2087
+ children: /* @__PURE__ */ jsx21(DatasheetFields, { record, look, wide: true })
1945
2088
  }
1946
2089
  ) : null,
1947
- open.has("raw") ? /* @__PURE__ */ jsx22(
2090
+ open.has("raw") ? /* @__PURE__ */ jsx21(
1948
2091
  Window,
1949
2092
  {
1950
2093
  id: "raw",
1951
2094
  title: TITLES.raw,
1952
2095
  subtitle,
1953
- icon: /* @__PURE__ */ jsx22(BracketsCurlyIcon, { weight: "bold" }),
1954
- action: /* @__PURE__ */ jsx22(CopyButton, { text: json }),
2096
+ icon: /* @__PURE__ */ jsx21(BracketsCurlyIcon, { weight: "bold" }),
2097
+ action: /* @__PURE__ */ jsx21(CopyButton, { text: json }),
1955
2098
  onClose: () => onClose("raw"),
1956
- children: /* @__PURE__ */ jsx22(RawRecord, { json, wide: true })
2099
+ children: /* @__PURE__ */ jsx21(RawRecord, { json, wide: true })
1957
2100
  }
1958
2101
  ) : null
1959
2102
  ] });
1960
2103
  };
1961
2104
 
1962
2105
  // src/feature-panel/feature-details.tsx
1963
- import { Fragment as Fragment10, jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
2106
+ import { Fragment as Fragment10, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
1964
2107
  var FeatureDetails = ({
1965
2108
  feature,
1966
2109
  units,
@@ -1980,8 +2123,8 @@ var FeatureDetails = ({
1980
2123
  folds
1981
2124
  }) => {
1982
2125
  const look = resolveLook(lookOption);
1983
- const name = /* @__PURE__ */ jsx23(FeatureName, { feature, label, look });
1984
- const [out, setOut] = useState12(/* @__PURE__ */ new Set());
2126
+ const name = /* @__PURE__ */ jsx22(FeatureName, { feature, label, look });
2127
+ const [out, setOut] = useState13(/* @__PURE__ */ new Set());
1985
2128
  const openOut = useCallback2((id) => setOut((was) => new Set(was).add(id)), []);
1986
2129
  const closeOut = useCallback2(
1987
2130
  (id) => setOut((was) => {
@@ -1991,31 +2134,31 @@ var FeatureDetails = ({
1991
2134
  }),
1992
2135
  []
1993
2136
  );
1994
- const popOut = PopOut ? (id) => /* @__PURE__ */ jsx23(PopOutButton, { id, onOpen: openOut }) : void 0;
1995
- return /* @__PURE__ */ jsxs21("div", { className: "flex flex-col gap-4 p-4", children: [
1996
- /* @__PURE__ */ jsxs21("div", { className: "flex flex-col gap-2", children: [
1997
- /* @__PURE__ */ jsxs21("div", { className: "flex items-start gap-2", children: [
1998
- /* @__PURE__ */ jsxs21("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
1999
- /* @__PURE__ */ jsx23("h3", { className: "truncate text-sm font-semibold text-gray-600 dark:text-zinc-100", children: name }),
2000
- /* @__PURE__ */ jsxs21("p", { className: cn12("flex flex-wrap items-center gap-1.5 text-xs", secondaryText(look)), children: [
2001
- /* @__PURE__ */ jsx23(Swatch, { color: directionColor, className: "size-2.5" }),
2137
+ const popOut = PopOut ? (id) => /* @__PURE__ */ jsx22(PopOutButton, { id, onOpen: openOut }) : void 0;
2138
+ return /* @__PURE__ */ jsxs19("div", { className: "flex flex-col gap-4 p-4", children: [
2139
+ /* @__PURE__ */ jsxs19("div", { className: "flex flex-col gap-2", children: [
2140
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-start gap-2", children: [
2141
+ /* @__PURE__ */ jsxs19("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
2142
+ /* @__PURE__ */ jsx22("h3", { className: "truncate text-sm font-semibold text-gray-600 dark:text-zinc-100", children: name }),
2143
+ /* @__PURE__ */ jsxs19("p", { className: cn12("flex flex-wrap items-center gap-1.5 text-xs", secondaryText(look)), children: [
2144
+ /* @__PURE__ */ jsx22(Swatch, { color: directionColor, className: "size-2.5" }),
2002
2145
  "Machined from ",
2003
2146
  directionLabel(feature.machiningDirection),
2004
- required ? /* @__PURE__ */ jsx23(RequiredBadge, {}) : null,
2147
+ required ? /* @__PURE__ */ jsx22(RequiredBadge, {}) : null,
2005
2148
  status
2006
2149
  ] })
2007
2150
  ] }),
2008
2151
  actions,
2009
- onFrame ? /* @__PURE__ */ jsx23(Button3, { variant: "secondary", size: "sm", onClick: onFrame, className: "shrink-0", children: /* @__PURE__ */ jsxs21("span", { className: "flex items-center gap-1 whitespace-nowrap", children: [
2010
- /* @__PURE__ */ jsx23(CrosshairIcon, { weight: "bold", className: "size-3.5" }),
2152
+ onFrame ? /* @__PURE__ */ jsx22(Button3, { variant: "secondary", size: "sm", onClick: onFrame, className: "shrink-0", children: /* @__PURE__ */ jsxs19("span", { className: "flex items-center gap-1 whitespace-nowrap", children: [
2153
+ /* @__PURE__ */ jsx22(CrosshairIcon, { weight: "bold", className: "size-3.5" }),
2011
2154
  "Show this feature"
2012
2155
  ] }) }) : null
2013
2156
  ] }),
2014
2157
  children
2015
2158
  ] }),
2016
- /* @__PURE__ */ jsx23(FoldSection, { id: "rules", title: "Rules broken", folds, children: /* @__PURE__ */ jsx23(BrokenRuleList, { rules, look }) }),
2017
- /* @__PURE__ */ jsx23(MeasurementSections, { measurements, look, folds }),
2018
- /* @__PURE__ */ jsx23(
2159
+ /* @__PURE__ */ jsx22(FoldSection, { id: "rules", title: "Rules broken", folds, children: /* @__PURE__ */ jsx22(BrokenRuleList, { rules, look }) }),
2160
+ /* @__PURE__ */ jsx22(MeasurementSections, { measurements, look, folds }),
2161
+ /* @__PURE__ */ jsx22(
2019
2162
  ReachSection2,
2020
2163
  {
2021
2164
  record,
@@ -2025,8 +2168,8 @@ var FeatureDetails = ({
2025
2168
  action: popOut?.("reach")
2026
2169
  }
2027
2170
  ),
2028
- /* @__PURE__ */ jsx23(RecordSections, { record, look, folds, popOut }),
2029
- PopOut ? /* @__PURE__ */ jsx23(
2171
+ /* @__PURE__ */ jsx22(RecordSections, { record, look, folds, popOut }),
2172
+ PopOut ? /* @__PURE__ */ jsx22(
2030
2173
  PopOuts,
2031
2174
  {
2032
2175
  open: out,
@@ -2044,14 +2187,14 @@ var FeatureDetails = ({
2044
2187
  var BrokenRuleList = ({
2045
2188
  rules,
2046
2189
  look
2047
- }) => rules.length > 0 ? /* @__PURE__ */ jsx23("ul", { className: "flex flex-col gap-1", children: rules.map((rule) => /* @__PURE__ */ jsxs21(
2190
+ }) => rules.length > 0 ? /* @__PURE__ */ jsx22("ul", { className: "flex flex-col gap-1", children: rules.map((rule) => /* @__PURE__ */ jsxs19(
2048
2191
  "li",
2049
2192
  {
2050
2193
  className: "flex items-start gap-2 text-sm text-gray-600 dark:text-zinc-100",
2051
2194
  children: [
2052
- /* @__PURE__ */ jsx23(Swatch, { color: rule.color, className: "mt-0.5" }),
2053
- /* @__PURE__ */ jsx23(Explained, { note: rule.note, children: /* @__PURE__ */ jsx23("span", { className: "min-w-0 flex-1", children: rule.text }) }),
2054
- rule.figure ? /* @__PURE__ */ jsxs21(
2195
+ /* @__PURE__ */ jsx22(Swatch, { color: rule.color, className: "mt-0.5" }),
2196
+ /* @__PURE__ */ jsx22(Explained, { note: rule.note, children: /* @__PURE__ */ jsx22("span", { className: "min-w-0 flex-1", children: rule.text }) }),
2197
+ rule.figure ? /* @__PURE__ */ jsxs19(
2055
2198
  "span",
2056
2199
  {
2057
2200
  className: cn12(
@@ -2061,15 +2204,15 @@ var BrokenRuleList = ({
2061
2204
  style: look.rules === "result" ? { color: rule.color } : void 0,
2062
2205
  children: [
2063
2206
  rule.figure,
2064
- look.rules === "result" && rule.limit ? /* @__PURE__ */ jsx23("span", { className: "ml-1 font-normal opacity-60", children: rule.limit }) : null
2207
+ look.rules === "result" && rule.limit ? /* @__PURE__ */ jsx22("span", { className: "ml-1 font-normal opacity-60", children: rule.limit }) : null
2065
2208
  ]
2066
2209
  }
2067
2210
  ) : null
2068
2211
  ]
2069
2212
  },
2070
2213
  rule.key
2071
- )) }) : /* @__PURE__ */ jsx23(None, { look });
2072
- var Explained = ({ note, children }) => note ? /* @__PURE__ */ jsx23(Tooltip, { tip: note, side: "left", children: /* @__PURE__ */ jsx23(
2214
+ )) }) : /* @__PURE__ */ jsx22(None, { look });
2215
+ var Explained = ({ note, children }) => note ? /* @__PURE__ */ jsx22(Tooltip, { tip: note, side: "left", children: /* @__PURE__ */ jsx22(
2073
2216
  "span",
2074
2217
  {
2075
2218
  tabIndex: 0,
@@ -2077,35 +2220,35 @@ var Explained = ({ note, children }) => note ? /* @__PURE__ */ jsx23(Tooltip, {
2077
2220
  children
2078
2221
  }
2079
2222
  ) }) : children;
2080
- var None = ({ look }) => /* @__PURE__ */ jsx23("p", { className: cn12("text-sm", secondaryText(look)), children: "None" });
2223
+ var None = ({ look }) => /* @__PURE__ */ jsx22("p", { className: cn12("text-sm", secondaryText(look)), children: "None" });
2081
2224
  var MeasurementSections = ({ measurements, look, folds }) => {
2082
2225
  if (measurements.status !== "ready") {
2083
- return /* @__PURE__ */ jsx23(Fragment10, { children: /* @__PURE__ */ jsx23(FoldSection, { id: "measurements", title: "Measurements", folds, children: measurements.status === "loading" ? /* @__PURE__ */ jsx23(Reading, { children: "Reading the feature\u2019s datasheet\u2026" }) : /* @__PURE__ */ jsx23(ReadFailure, { failure: measurements }) }) });
2226
+ return /* @__PURE__ */ jsx22(Fragment10, { children: /* @__PURE__ */ jsx22(FoldSection, { id: "measurements", title: "Measurements", folds, children: measurements.status === "loading" ? /* @__PURE__ */ jsx22(Reading, { children: "Reading the feature\u2019s datasheet\u2026" }) : /* @__PURE__ */ jsx22(ReadFailure, { failure: measurements }) }) });
2084
2227
  }
2085
2228
  const measured = measurements.value.filter((row) => !row.milling);
2086
2229
  const milling = measurements.value.filter((row) => row.milling);
2087
- return /* @__PURE__ */ jsxs21(Fragment10, { children: [
2088
- /* @__PURE__ */ jsx23(FoldSection, { id: "measurements", title: "Measurements", folds, children: /* @__PURE__ */ jsx23(MeasurementList, { rows: measured, look }) }),
2089
- milling.length > 0 ? /* @__PURE__ */ jsx23(FoldSection, { id: "milling", title: "Milling considerations", folds, children: /* @__PURE__ */ jsx23(MeasurementList, { rows: milling, look }) }) : null
2230
+ return /* @__PURE__ */ jsxs19(Fragment10, { children: [
2231
+ /* @__PURE__ */ jsx22(FoldSection, { id: "measurements", title: "Measurements", folds, children: /* @__PURE__ */ jsx22(MeasurementList, { rows: measured, look }) }),
2232
+ milling.length > 0 ? /* @__PURE__ */ jsx22(FoldSection, { id: "milling", title: "Milling considerations", folds, children: /* @__PURE__ */ jsx22(MeasurementList, { rows: milling, look }) }) : null
2090
2233
  ] });
2091
2234
  };
2092
- var MeasurementList = ({ rows, look }) => rows.length > 0 ? /* @__PURE__ */ jsx23("dl", { className: "grid grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 text-sm", children: rows.map((row) => /* @__PURE__ */ jsxs21("div", { className: "contents", children: [
2093
- /* @__PURE__ */ jsxs21("dt", { className: cn12("flex items-center gap-1", secondaryText(look)), children: [
2235
+ var MeasurementList = ({ rows, look }) => rows.length > 0 ? /* @__PURE__ */ jsx22("dl", { className: "grid grid-cols-[1fr_auto] items-center gap-x-3 gap-y-1 text-sm", children: rows.map((row) => /* @__PURE__ */ jsxs19("div", { className: "contents", children: [
2236
+ /* @__PURE__ */ jsxs19("dt", { className: cn12("flex items-center gap-1", secondaryText(look)), children: [
2094
2237
  row.label,
2095
- /* @__PURE__ */ jsx23(Derivation, { lines: row.derivation, look })
2238
+ /* @__PURE__ */ jsx22(Derivation, { lines: row.derivation, look })
2096
2239
  ] }),
2097
- /* @__PURE__ */ jsx23("dd", { className: "text-right text-gray-600 tabular-nums dark:text-zinc-100", title: row.alt, children: row.value })
2098
- ] }, row.key)) }) : /* @__PURE__ */ jsx23(None, { look });
2240
+ /* @__PURE__ */ jsx22("dd", { className: "text-right text-gray-600 tabular-nums dark:text-zinc-100", title: row.alt, children: row.value })
2241
+ ] }, row.key)) }) : /* @__PURE__ */ jsx22(None, { look });
2099
2242
  var Derivation = ({
2100
2243
  lines,
2101
2244
  look
2102
- }) => lines.length > 0 ? /* @__PURE__ */ jsx23(
2245
+ }) => lines.length > 0 ? /* @__PURE__ */ jsx22(
2103
2246
  Tooltip,
2104
2247
  {
2105
2248
  side: "left",
2106
2249
  hoverable: true,
2107
- tip: /* @__PURE__ */ jsx23("div", { className: "flex max-w-sm flex-col gap-0.5 font-mono text-2xs leading-snug whitespace-pre-wrap", children: lines.map((line, index) => /* @__PURE__ */ jsx23("span", { children: line }, index)) }),
2108
- children: /* @__PURE__ */ jsx23(
2250
+ tip: /* @__PURE__ */ jsx22("div", { className: "flex max-w-sm flex-col gap-0.5 font-mono text-2xs leading-snug whitespace-pre-wrap", children: lines.map((line, index) => /* @__PURE__ */ jsx22("span", { children: line }, index)) }),
2251
+ children: /* @__PURE__ */ jsx22(
2109
2252
  "button",
2110
2253
  {
2111
2254
  type: "button",
@@ -2114,7 +2257,7 @@ var Derivation = ({
2114
2257
  "shrink-0 rounded outline-none hover:text-gray-500 focus-visible:ring-2 focus-visible:ring-info/75 dark:hover:text-zinc-300",
2115
2258
  faintText(look)
2116
2259
  ),
2117
- children: /* @__PURE__ */ jsx23(InfoIcon, { weight: "bold", className: "size-3" })
2260
+ children: /* @__PURE__ */ jsx22(InfoIcon, { weight: "bold", className: "size-3" })
2118
2261
  }
2119
2262
  )
2120
2263
  }
@@ -2125,19 +2268,19 @@ var ReachSection2 = ({ record, units, profile, folds, action }) => {
2125
2268
  [record]
2126
2269
  );
2127
2270
  if (record.status === "ready" && !curve) return null;
2128
- return /* @__PURE__ */ jsx23(FoldSection, { id: "reach", title: "Reach", folds, action: curve ? action : null, children: record.status === "loading" ? /* @__PURE__ */ jsx23(Reading, { children: "Reading the reach curve\u2026" }) : record.status === "error" ? /* @__PURE__ */ jsx23(ReadFailure, { failure: record }) : curve ? /* @__PURE__ */ jsx23(ReachChart, { curve, units, feature: profile }) : null });
2271
+ return /* @__PURE__ */ jsx22(FoldSection, { id: "reach", title: "Reach", folds, action: curve ? action : null, children: record.status === "loading" ? /* @__PURE__ */ jsx22(Reading, { children: "Reading the reach curve\u2026" }) : record.status === "error" ? /* @__PURE__ */ jsx22(ReadFailure, { failure: record }) : curve ? /* @__PURE__ */ jsx22(ReachChart, { curve, units, feature: profile }) : null });
2129
2272
  };
2130
2273
 
2131
2274
  // src/feature-panel/candidate-list.tsx
2132
2275
  import { XIcon } from "@phosphor-icons/react";
2133
2276
  import { IconButton as IconButton4, cn as cn13 } from "@toolpath/ui";
2134
- import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
2277
+ import { jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
2135
2278
  var candidateListKeys = listKeys("candidates");
2136
2279
  var faceLetter = (index) => String.fromCharCode(65 + index % 26);
2137
2280
  var FaceLetter = ({
2138
2281
  index,
2139
2282
  active
2140
- }) => /* @__PURE__ */ jsx24(
2283
+ }) => /* @__PURE__ */ jsx23(
2141
2284
  "span",
2142
2285
  {
2143
2286
  className: cn13(
@@ -2158,27 +2301,27 @@ var CandidateList = ({
2158
2301
  }) => {
2159
2302
  const look = resolveLook(lookOption);
2160
2303
  const count = `Candidate features (${rows.length})`;
2161
- return /* @__PURE__ */ jsx24("div", { className: "border-b border-gray-100 p-2 dark:border-zinc-800", children: /* @__PURE__ */ jsx24(
2304
+ return /* @__PURE__ */ jsx23("div", { className: "border-b border-gray-100 p-2 dark:border-zinc-800", children: /* @__PURE__ */ jsx23(
2162
2305
  FoldSection,
2163
2306
  {
2164
2307
  id: face ? `candidates-${faceLetter(face.index)}` : "candidates",
2165
- title: face ? /* @__PURE__ */ jsxs22("span", { className: "flex items-center gap-1.5", children: [
2166
- /* @__PURE__ */ jsx24(FaceLetter, { index: face.index, active: face.active }),
2308
+ title: face ? /* @__PURE__ */ jsxs20("span", { className: "flex items-center gap-1.5", children: [
2309
+ /* @__PURE__ */ jsx23(FaceLetter, { index: face.index, active: face.active }),
2167
2310
  count
2168
2311
  ] }) : count,
2169
- action: face?.onStop ? /* @__PURE__ */ jsx24(
2312
+ action: face?.onStop ? /* @__PURE__ */ jsx23(
2170
2313
  IconButton4,
2171
2314
  {
2172
2315
  "aria-label": `Stop comparing face ${faceLetter(face.index)}`,
2173
2316
  variant: "muted",
2174
2317
  size: "sm",
2175
2318
  onClick: face.onStop,
2176
- children: /* @__PURE__ */ jsx24(XIcon, { weight: "bold" })
2319
+ children: /* @__PURE__ */ jsx23(XIcon, { weight: "bold" })
2177
2320
  }
2178
2321
  ) : null,
2179
2322
  folds,
2180
2323
  className: "px-2",
2181
- children: /* @__PURE__ */ jsx24("ul", { className: "-mx-2 flex flex-col gap-0.5", children: rows.map((row) => /* @__PURE__ */ jsx24(
2324
+ children: /* @__PURE__ */ jsx23("ul", { className: "-mx-2 flex flex-col gap-0.5", children: rows.map((row) => /* @__PURE__ */ jsx23(
2182
2325
  Row,
2183
2326
  {
2184
2327
  row,
@@ -2195,7 +2338,7 @@ var CandidateList = ({
2195
2338
  var Row = ({ row, chosen, onSelect, onHover, look }) => {
2196
2339
  const { feature } = row;
2197
2340
  const info = look.selected === "info";
2198
- return /* @__PURE__ */ jsxs22(
2341
+ return /* @__PURE__ */ jsxs20(
2199
2342
  "li",
2200
2343
  {
2201
2344
  className: cn13(
@@ -2204,7 +2347,7 @@ var Row = ({ row, chosen, onSelect, onHover, look }) => {
2204
2347
  info && (chosen ? "border-info/60 bg-info/15" : "border-transparent")
2205
2348
  ),
2206
2349
  children: [
2207
- /* @__PURE__ */ jsxs22(
2350
+ /* @__PURE__ */ jsxs20(
2208
2351
  "button",
2209
2352
  {
2210
2353
  type: "button",
@@ -2222,18 +2365,18 @@ var Row = ({ row, chosen, onSelect, onHover, look }) => {
2222
2365
  chosen && (info ? "font-medium" : "bg-gray-100 font-semibold dark:bg-zinc-800")
2223
2366
  ),
2224
2367
  children: [
2225
- /* @__PURE__ */ jsxs22("span", { className: "flex w-full items-center gap-2", children: [
2226
- row.color ? /* @__PURE__ */ jsx24(Swatch, { color: row.color }) : /* @__PURE__ */ jsx24("span", { "aria-hidden": true, className: "size-4 shrink-0" }),
2227
- /* @__PURE__ */ jsx24("span", { className: "min-w-0 flex-1 truncate", children: /* @__PURE__ */ jsx24(FeatureName, { feature, label: row.label, look }) }),
2228
- row.ruleCount ? /* @__PURE__ */ jsxs22("span", { className: cn13("text-2xs tabular-nums", secondaryText(look)), children: [
2368
+ /* @__PURE__ */ jsxs20("span", { className: "flex w-full items-center gap-2", children: [
2369
+ row.color ? /* @__PURE__ */ jsx23(Swatch, { color: row.color }) : /* @__PURE__ */ jsx23("span", { "aria-hidden": true, className: "size-4 shrink-0" }),
2370
+ /* @__PURE__ */ jsx23("span", { className: "min-w-0 flex-1 truncate", children: /* @__PURE__ */ jsx23(FeatureName, { feature, label: row.label, look }) }),
2371
+ row.ruleCount ? /* @__PURE__ */ jsxs20("span", { className: cn13("text-2xs tabular-nums", secondaryText(look)), children: [
2229
2372
  row.ruleCount,
2230
2373
  " ",
2231
2374
  row.ruleCount === 1 ? "rule" : "rules"
2232
2375
  ] }) : null,
2233
- row.required ? /* @__PURE__ */ jsx24(RequiredBadge, {}) : null,
2234
- row.detail ? /* @__PURE__ */ jsx24("span", { className: cn13("max-w-32 shrink-0 truncate text-xs", secondaryText(look)), children: row.detail }) : null
2376
+ row.required ? /* @__PURE__ */ jsx23(RequiredBadge, {}) : null,
2377
+ row.detail ? /* @__PURE__ */ jsx23("span", { className: cn13("max-w-32 shrink-0 truncate text-xs", secondaryText(look)), children: row.detail }) : null
2235
2378
  ] }),
2236
- row.note ? /* @__PURE__ */ jsx24("span", { className: "pl-6 text-2xs text-warning-darken", children: row.note }) : null
2379
+ row.note ? /* @__PURE__ */ jsx23("span", { className: "pl-6 text-2xs text-warning-darken", children: row.note }) : null
2237
2380
  ]
2238
2381
  }
2239
2382
  ),
@@ -2246,7 +2389,7 @@ var Row = ({ row, chosen, onSelect, onHover, look }) => {
2246
2389
  // src/feature-panel/comparison-table.tsx
2247
2390
  import { useMemo as useMemo8 } from "react";
2248
2391
  import { cn as cn14 } from "@toolpath/ui";
2249
- import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
2392
+ import { jsx as jsx24, jsxs as jsxs21 } from "react/jsx-runtime";
2250
2393
  var DASH = "\u2014";
2251
2394
  var ComparisonTable = ({
2252
2395
  faces,
@@ -2274,8 +2417,8 @@ var ComparisonTable = ({
2274
2417
  const text = directionLabel(feature.machiningDirection);
2275
2418
  return {
2276
2419
  text,
2277
- node: /* @__PURE__ */ jsxs23("span", { className: "inline-flex items-center gap-1", children: [
2278
- /* @__PURE__ */ jsx25(Swatch, { color: face.directionColor, className: "size-2.5" }),
2420
+ node: /* @__PURE__ */ jsxs21("span", { className: "inline-flex items-center gap-1", children: [
2421
+ /* @__PURE__ */ jsx24(Swatch, { color: face.directionColor, className: "size-2.5" }),
2279
2422
  text
2280
2423
  ] })
2281
2424
  };
@@ -2283,14 +2426,14 @@ var ComparisonTable = ({
2283
2426
  ...faces.some((face) => face.required !== void 0) ? [row("required", "Required", (face) => ({ text: face.required ? "Yes" : "No" }))] : [],
2284
2427
  row("rules", "Rules broken", (face) => ({
2285
2428
  text: face.rules.map((rule) => rule.key).join(",") || "None",
2286
- node: face.rules.length > 0 ? /* @__PURE__ */ jsxs23(
2429
+ node: face.rules.length > 0 ? /* @__PURE__ */ jsxs21(
2287
2430
  "span",
2288
2431
  {
2289
2432
  className: "inline-flex items-center gap-0.5",
2290
2433
  title: face.rules.map((rule) => rule.text).join("\n"),
2291
2434
  children: [
2292
- face.rules.map((rule) => /* @__PURE__ */ jsx25(Swatch, { color: rule.color, className: "size-2.5" }, rule.key)),
2293
- /* @__PURE__ */ jsx25("span", { className: "ml-0.5", children: face.rules.length })
2435
+ face.rules.map((rule) => /* @__PURE__ */ jsx24(Swatch, { color: rule.color, className: "size-2.5" }, rule.key)),
2436
+ /* @__PURE__ */ jsx24("span", { className: "ml-0.5", children: face.rules.length })
2294
2437
  ]
2295
2438
  }
2296
2439
  ) : "None"
@@ -2304,16 +2447,16 @@ var ComparisonTable = ({
2304
2447
  return {
2305
2448
  key,
2306
2449
  label,
2307
- cells: values.map((value) => value ? /* @__PURE__ */ jsx25("span", { title: value.alt, children: value.value }) : DASH),
2450
+ cells: values.map((value) => value ? /* @__PURE__ */ jsx24("span", { title: value.alt, children: value.value }) : DASH),
2308
2451
  differs: new Set(values.map((value) => value?.value ?? DASH)).size > 1
2309
2452
  };
2310
2453
  });
2311
2454
  return [...fixed, ...measured];
2312
2455
  }, [faces]);
2313
- return /* @__PURE__ */ jsx25("div", { className: "border-b border-gray-100 p-4 dark:border-zinc-800", children: /* @__PURE__ */ jsx25(FoldSection, { id: "comparison", title: "Comparison", folds, children: /* @__PURE__ */ jsx25("div", { className: "-mx-1 overflow-x-auto", children: /* @__PURE__ */ jsxs23("table", { className: "w-full border-separate border-spacing-x-1 border-spacing-y-0.5 text-sm", children: [
2314
- /* @__PURE__ */ jsx25("thead", { children: /* @__PURE__ */ jsxs23("tr", { children: [
2315
- /* @__PURE__ */ jsx25("th", { className: "w-0" }),
2316
- faces.map(({ feature }, index) => /* @__PURE__ */ jsx25("th", { className: "text-left align-bottom font-normal", children: /* @__PURE__ */ jsxs23(
2456
+ return /* @__PURE__ */ jsx24("div", { className: "border-b border-gray-100 p-4 dark:border-zinc-800", children: /* @__PURE__ */ jsx24(FoldSection, { id: "comparison", title: "Comparison", folds, children: /* @__PURE__ */ jsx24("div", { className: "-mx-1 overflow-x-auto", children: /* @__PURE__ */ jsxs21("table", { className: "w-full border-separate border-spacing-x-1 border-spacing-y-0.5 text-sm", children: [
2457
+ /* @__PURE__ */ jsx24("thead", { children: /* @__PURE__ */ jsxs21("tr", { children: [
2458
+ /* @__PURE__ */ jsx24("th", { className: "w-0" }),
2459
+ faces.map(({ feature }, index) => /* @__PURE__ */ jsx24("th", { className: "text-left align-bottom font-normal", children: /* @__PURE__ */ jsxs21(
2317
2460
  "button",
2318
2461
  {
2319
2462
  type: "button",
@@ -2328,14 +2471,14 @@ var ComparisonTable = ({
2328
2471
  index === active && "bg-gray-100 font-semibold dark:bg-zinc-800"
2329
2472
  ),
2330
2473
  children: [
2331
- /* @__PURE__ */ jsx25(FaceLetter, { index, active: index === active }),
2332
- /* @__PURE__ */ jsx25("span", { className: "min-w-0 truncate", children: feature ? /* @__PURE__ */ jsx25(FeatureName, { feature, look }) : "No feature" })
2474
+ /* @__PURE__ */ jsx24(FaceLetter, { index, active: index === active }),
2475
+ /* @__PURE__ */ jsx24("span", { className: "min-w-0 truncate", children: feature ? /* @__PURE__ */ jsx24(FeatureName, { feature, look }) : "No feature" })
2333
2476
  ]
2334
2477
  }
2335
2478
  ) }, index))
2336
2479
  ] }) }),
2337
- /* @__PURE__ */ jsx25("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs23("tr", { "data-differs": row.differs || void 0, children: [
2338
- /* @__PURE__ */ jsx25(
2480
+ /* @__PURE__ */ jsx24("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs21("tr", { "data-differs": row.differs || void 0, children: [
2481
+ /* @__PURE__ */ jsx24(
2339
2482
  "th",
2340
2483
  {
2341
2484
  scope: "row",
@@ -2346,7 +2489,7 @@ var ComparisonTable = ({
2346
2489
  children: row.label
2347
2490
  }
2348
2491
  ),
2349
- row.cells.map((cell, index) => /* @__PURE__ */ jsx25(
2492
+ row.cells.map((cell, index) => /* @__PURE__ */ jsx24(
2350
2493
  "td",
2351
2494
  {
2352
2495
  className: cn14(
@@ -2364,7 +2507,7 @@ var ComparisonTable = ({
2364
2507
  // src/feature-panel/feature-panel.tsx
2365
2508
  import { CubeIcon, XIcon as XIcon2 } from "@phosphor-icons/react";
2366
2509
  import { IconButton as IconButton5, ScrollArea as ScrollArea2, cn as cn15 } from "@toolpath/ui";
2367
- import { Fragment as Fragment11, jsx as jsx26, jsxs as jsxs24 } from "react/jsx-runtime";
2510
+ import { Fragment as Fragment11, jsx as jsx25, jsxs as jsxs22 } from "react/jsx-runtime";
2368
2511
  var title = (faces, active, group) => {
2369
2512
  if (faces.length > 1) return `Comparing ${faces.length} faces`;
2370
2513
  if (faces[active]?.region !== null && faces[active]?.region !== void 0)
@@ -2391,7 +2534,7 @@ var FeaturePanel = ({
2391
2534
  const comparing = faces.length > 1;
2392
2535
  const face = faces[active];
2393
2536
  const heading = title(faces, active, group);
2394
- return /* @__PURE__ */ jsxs24(
2537
+ return /* @__PURE__ */ jsxs22(
2395
2538
  "section",
2396
2539
  {
2397
2540
  "aria-label": heading,
@@ -2407,7 +2550,7 @@ var FeaturePanel = ({
2407
2550
  className
2408
2551
  ),
2409
2552
  children: [
2410
- /* @__PURE__ */ jsxs24(
2553
+ /* @__PURE__ */ jsxs22(
2411
2554
  "header",
2412
2555
  {
2413
2556
  ...headerProps,
@@ -2416,15 +2559,15 @@ var FeaturePanel = ({
2416
2559
  headerProps?.className
2417
2560
  ),
2418
2561
  children: [
2419
- /* @__PURE__ */ jsx26(CubeIcon, { weight: "bold", className: cn15("size-5 shrink-0", secondaryText(look)) }),
2420
- /* @__PURE__ */ jsx26("h2", { className: "flex-1 text-sm font-semibold text-gray-600 dark:text-zinc-100", children: heading }),
2421
- /* @__PURE__ */ jsx26(IconButton5, { "aria-label": "Close", variant: "muted", onClick: onClose, children: /* @__PURE__ */ jsx26(XIcon2, { weight: "bold" }) })
2562
+ /* @__PURE__ */ jsx25(CubeIcon, { weight: "bold", className: cn15("size-5 shrink-0", secondaryText(look)) }),
2563
+ /* @__PURE__ */ jsx25("h2", { className: "flex-1 text-sm font-semibold text-gray-600 dark:text-zinc-100", children: heading }),
2564
+ /* @__PURE__ */ jsx25(IconButton5, { "aria-label": "Close", variant: "muted", onClick: onClose, children: /* @__PURE__ */ jsx25(XIcon2, { weight: "bold" }) })
2422
2565
  ]
2423
2566
  }
2424
2567
  ),
2425
- /* @__PURE__ */ jsxs24(ScrollArea2, { className: "min-h-0 flex-1", children: [
2426
- comparing ? /* @__PURE__ */ jsxs24(Fragment11, { children: [
2427
- faces.map((each, index) => /* @__PURE__ */ jsx26(
2568
+ /* @__PURE__ */ jsxs22(ScrollArea2, { className: "min-h-0 flex-1", children: [
2569
+ comparing ? /* @__PURE__ */ jsxs22(Fragment11, { children: [
2570
+ faces.map((each, index) => /* @__PURE__ */ jsx25(
2428
2571
  CandidateList,
2429
2572
  {
2430
2573
  rows: each.rows,
@@ -2441,7 +2584,7 @@ var FeaturePanel = ({
2441
2584
  },
2442
2585
  `${each.region ?? "list"}-${index}`
2443
2586
  )),
2444
- comparison ? /* @__PURE__ */ jsx26(
2587
+ comparison ? /* @__PURE__ */ jsx25(
2445
2588
  ComparisonTable,
2446
2589
  {
2447
2590
  faces: comparison,
@@ -2452,7 +2595,7 @@ var FeaturePanel = ({
2452
2595
  folds
2453
2596
  }
2454
2597
  ) : null
2455
- ] }) : face && face.region !== null ? /* @__PURE__ */ jsx26(
2598
+ ] }) : face && face.region !== null ? /* @__PURE__ */ jsx25(
2456
2599
  CandidateList,
2457
2600
  {
2458
2601
  rows: face.rows,
@@ -2463,7 +2606,7 @@ var FeaturePanel = ({
2463
2606
  folds
2464
2607
  }
2465
2608
  ) : null,
2466
- face?.selected ? children : /* @__PURE__ */ jsx26("p", { className: cn15("px-4 py-3 text-sm", secondaryText(look)), children: face && face.rows.length > 0 ? "Choose a feature to read its details." : "No feature owns this face." })
2609
+ face?.selected ? children : /* @__PURE__ */ jsx25("p", { className: cn15("px-4 py-3 text-sm", secondaryText(look)), children: face && face.rows.length > 0 ? "Choose a feature to read its details." : "No feature owns this face." })
2467
2610
  ] })
2468
2611
  ]
2469
2612
  }