@toolpath/dfm 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +514 -376
  2. package/package.json +1 -1
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,14 @@ 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
964
  var RuleSetActions = ({ rules }) => {
827
965
  const [note, setNote] = useTimedValue(NOTE_MS);
828
966
  const [copied, setCopied] = useTimedValue(COPIED_MS);
829
- const fileInput = useRef3(null);
967
+ const fileInput = useRef4(null);
830
968
  const importRules = async (file) => {
831
969
  if (!file) return;
832
970
  let text;
@@ -854,23 +992,23 @@ var RuleSetActions = ({ rules }) => {
854
992
  setCopied(true);
855
993
  setNote("Paste the prompt into an LLM, then import the JSON it writes.");
856
994
  };
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(
995
+ return /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-2 px-4 pt-2 pb-3", children: [
996
+ /* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap items-center gap-2", children: [
997
+ /* @__PURE__ */ jsx11(Button, { variant: "secondary", onClick: () => fileInput.current?.click(), children: /* @__PURE__ */ jsx11(ActionLabel, { icon: /* @__PURE__ */ jsx11(UploadSimpleIcon, { weight: "bold" }), children: "Import\u2026" }) }),
998
+ /* @__PURE__ */ jsx11(Button, { variant: "secondary", onClick: () => downloadRuleSet(rules), children: /* @__PURE__ */ jsx11(ActionLabel, { icon: /* @__PURE__ */ jsx11(DownloadSimpleIcon, { weight: "bold" }), children: "Export" }) }),
999
+ /* @__PURE__ */ jsx11(
862
1000
  Button,
863
1001
  {
864
1002
  variant: "secondary",
865
1003
  title: "Copies a prompt that walks an LLM through writing a rule set for you",
866
1004
  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" })
1005
+ children: /* @__PURE__ */ jsxs8("span", { className: "grid justify-items-center *:col-start-1 *:row-start-1", children: [
1006
+ /* @__PURE__ */ jsx11(ActionLabel, { hidden: copied !== null, icon: /* @__PURE__ */ jsx11(CopyIcon, { weight: "bold" }), children: "Copy LLM prompt" }),
1007
+ /* @__PURE__ */ jsx11(ActionLabel, { hidden: copied === null, icon: /* @__PURE__ */ jsx11(CheckIcon2, { weight: "bold" }), children: "Copied" })
870
1008
  ] })
871
1009
  }
872
1010
  ),
873
- /* @__PURE__ */ jsx12(
1011
+ /* @__PURE__ */ jsx11(
874
1012
  "input",
875
1013
  {
876
1014
  ref: fileInput,
@@ -885,10 +1023,10 @@ var RuleSetActions = ({ rules }) => {
885
1023
  }
886
1024
  )
887
1025
  ] }),
888
- note ? /* @__PURE__ */ jsx12("p", { className: "text-xs text-gray-500 dark:text-zinc-400", role: "status", children: note }) : null
1026
+ note ? /* @__PURE__ */ jsx11("p", { className: "text-xs text-gray-500 dark:text-zinc-400", role: "status", children: note }) : null
889
1027
  ] });
890
1028
  };
891
- var ActionLabel = ({ icon, children, hidden = false }) => /* @__PURE__ */ jsxs10(
1029
+ var ActionLabel = ({ icon, children, hidden = false }) => /* @__PURE__ */ jsxs8(
892
1030
  "span",
893
1031
  {
894
1032
  "aria-hidden": hidden || void 0,
@@ -917,7 +1055,7 @@ var importedNote = (count, skipped, name) => {
917
1055
  };
918
1056
 
919
1057
  // src/rule-list/rule-list.tsx
920
- import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1058
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
921
1059
  var ruleListKeys = listKeys("rules");
922
1060
  var RuleList = ({
923
1061
  rules,
@@ -927,8 +1065,8 @@ var RuleList = ({
927
1065
  units,
928
1066
  ...list
929
1067
  }) => {
930
- const [openIds, setOpenIds] = useState7(/* @__PURE__ */ new Set());
931
- const [added, setAdded] = useState7(null);
1068
+ const [openIds, setOpenIds] = useState8(/* @__PURE__ */ new Set());
1069
+ const [added, setAdded] = useState8(null);
932
1070
  const toggleOpen = (id) => setOpenIds((was) => {
933
1071
  const next = new Set(was);
934
1072
  if (!next.delete(id)) next.add(id);
@@ -944,7 +1082,7 @@ var RuleList = ({
944
1082
  };
945
1083
  const active = rules.rules.filter(found);
946
1084
  const none = rules.rules.filter((rule) => !found(rule));
947
- const row = (rule) => /* @__PURE__ */ jsx13(
1085
+ const row = (rule) => /* @__PURE__ */ jsx12(
948
1086
  RuleRow,
949
1087
  {
950
1088
  rule,
@@ -965,11 +1103,11 @@ var RuleList = ({
965
1103
  );
966
1104
  return (
967
1105
  // 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: [
1106
+ /* @__PURE__ */ jsxs9(ScrollArea, { className: "min-h-0 flex-auto", children: [
1107
+ /* @__PURE__ */ jsxs9("ul", { className: "flex flex-col gap-0.5 pt-2 pr-4 pl-2", children: [
970
1108
  active.map(row),
971
1109
  none.map(row),
972
- /* @__PURE__ */ jsx13(
1110
+ /* @__PURE__ */ jsx12(
973
1111
  NewRule,
974
1112
  {
975
1113
  onAdd: (subject) => {
@@ -980,7 +1118,7 @@ var RuleList = ({
980
1118
  }
981
1119
  )
982
1120
  ] }),
983
- /* @__PURE__ */ jsx13(RuleSetActions, { rules })
1121
+ /* @__PURE__ */ jsx12(RuleSetActions, { rules })
984
1122
  ] })
985
1123
  );
986
1124
  };
@@ -989,7 +1127,7 @@ var RuleList = ({
989
1127
  import { useMemo as useMemo4 } from "react";
990
1128
 
991
1129
  // src/reach/reach-section.tsx
992
- import { useId, useMemo as useMemo3, useState as useState8 } from "react";
1130
+ import { useId as useId2, useMemo as useMemo3, useState as useState9 } from "react";
993
1131
 
994
1132
  // src/reach/reading.tsx
995
1133
  import { heightAt } from "@toolpath/tool-support";
@@ -1064,7 +1202,7 @@ var READ = "stroke-sky-600 dark:stroke-sky-400";
1064
1202
  var READ_DOT = "fill-sky-600 dark:fill-sky-400";
1065
1203
 
1066
1204
  // src/reach/reading.tsx
1067
- import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1205
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1068
1206
  var readingAt = ({ offsets, heights }, offset) => ({
1069
1207
  offset,
1070
1208
  height: heightAt({ horizontalOffset: offsets, verticalOffset: heights }, offset),
@@ -1087,8 +1225,8 @@ var ReadingMark = ({
1087
1225
  const pointerX = x(reading.offset);
1088
1226
  const onRight = pointerX + READ_INSET + textWidth <= width - READ_INSET;
1089
1227
  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(
1228
+ return /* @__PURE__ */ jsxs10("g", { pointerEvents: "none", children: [
1229
+ /* @__PURE__ */ jsx13(
1092
1230
  "line",
1093
1231
  {
1094
1232
  x1: pointerX,
@@ -1100,8 +1238,8 @@ var ReadingMark = ({
1100
1238
  className: READ
1101
1239
  }
1102
1240
  ),
1103
- /* @__PURE__ */ jsx14("circle", { cx: pointerX, cy: y(reading.height), r: 4, className: READ_DOT }),
1104
- /* @__PURE__ */ jsx14(
1241
+ /* @__PURE__ */ jsx13("circle", { cx: pointerX, cy: y(reading.height), r: 4, className: READ_DOT }),
1242
+ /* @__PURE__ */ jsx13(
1105
1243
  "text",
1106
1244
  {
1107
1245
  x: labelX,
@@ -1200,8 +1338,8 @@ var sectionGeometry = (whole, feature, width) => {
1200
1338
  };
1201
1339
 
1202
1340
  // 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(
1341
+ import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1342
+ var Arrows = ({ id }) => /* @__PURE__ */ jsx14("defs", { children: /* @__PURE__ */ jsx14(
1205
1343
  "marker",
1206
1344
  {
1207
1345
  id,
@@ -1211,14 +1349,14 @@ var Arrows = ({ id }) => /* @__PURE__ */ jsx15("defs", { children: /* @__PURE__
1211
1349
  markerWidth: "7",
1212
1350
  markerHeight: "7",
1213
1351
  orient: "auto-start-reverse",
1214
- children: /* @__PURE__ */ jsx15("path", { d: "M 0 1.5 L 10 5 L 0 8.5 z", className: ARROW })
1352
+ children: /* @__PURE__ */ jsx14("path", { d: "M 0 1.5 L 10 5 L 0 8.5 z", className: ARROW })
1215
1353
  }
1216
1354
  ) });
1217
1355
  var SectionShapes = ({ geometry }) => {
1218
1356
  const { x, floorY, right, featureTopY, featureBottomY, wallTop, feature } = geometry;
1219
1357
  const floorLeft = feature.through ? x(0) : SECTION_PAD.left;
1220
- return /* @__PURE__ */ jsxs13(Fragment4, { children: [
1221
- /* @__PURE__ */ jsx15(
1358
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1359
+ /* @__PURE__ */ jsx14(
1222
1360
  "rect",
1223
1361
  {
1224
1362
  x: SECTION_PAD.left,
@@ -1228,7 +1366,7 @@ var SectionShapes = ({ geometry }) => {
1228
1366
  className: FEATURE_FILL
1229
1367
  }
1230
1368
  ),
1231
- /* @__PURE__ */ jsx15(
1369
+ /* @__PURE__ */ jsx14(
1232
1370
  "rect",
1233
1371
  {
1234
1372
  x: floorLeft,
@@ -1238,9 +1376,9 @@ var SectionShapes = ({ geometry }) => {
1238
1376
  className: FLOOR
1239
1377
  }
1240
1378
  ),
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(
1379
+ /* @__PURE__ */ jsx14("path", { d: geometry.materialPath, strokeWidth: 1, className: MATERIAL }),
1380
+ /* @__PURE__ */ jsx14("line", { x1: floorLeft, x2: right, y1: floorY, y2: floorY, strokeWidth: 1, className: LINE }),
1381
+ feature.through ? null : /* @__PURE__ */ jsx14(
1244
1382
  "line",
1245
1383
  {
1246
1384
  x1: SECTION_PAD.left,
@@ -1251,7 +1389,7 @@ var SectionShapes = ({ geometry }) => {
1251
1389
  className: FEATURE_LINE
1252
1390
  }
1253
1391
  ),
1254
- /* @__PURE__ */ jsx15(
1392
+ /* @__PURE__ */ jsx14(
1255
1393
  "line",
1256
1394
  {
1257
1395
  x1: x(0),
@@ -1262,7 +1400,7 @@ var SectionShapes = ({ geometry }) => {
1262
1400
  className: FEATURE_LINE
1263
1401
  }
1264
1402
  ),
1265
- /* @__PURE__ */ jsx15(
1403
+ /* @__PURE__ */ jsx14(
1266
1404
  "line",
1267
1405
  {
1268
1406
  x1: x(0),
@@ -1278,8 +1416,8 @@ var SectionShapes = ({ geometry }) => {
1278
1416
  };
1279
1417
  var SectionHeadings = ({ geometry }) => {
1280
1418
  const { right } = geometry;
1281
- return /* @__PURE__ */ jsxs13(Fragment4, { children: [
1282
- /* @__PURE__ */ jsx15(
1419
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1420
+ /* @__PURE__ */ jsx14(
1283
1421
  "text",
1284
1422
  {
1285
1423
  x: geometry.featureHeadingX,
@@ -1291,7 +1429,7 @@ var SectionHeadings = ({ geometry }) => {
1291
1429
  children: geometry.featureHeading
1292
1430
  }
1293
1431
  ),
1294
- /* @__PURE__ */ jsx15(
1432
+ /* @__PURE__ */ jsx14(
1295
1433
  "text",
1296
1434
  {
1297
1435
  x: right,
@@ -1305,7 +1443,7 @@ var SectionHeadings = ({ geometry }) => {
1305
1443
  )
1306
1444
  ] });
1307
1445
  };
1308
- var FeatureBreak = ({ geometry }) => geometry.featureCut ? /* @__PURE__ */ jsx15(
1446
+ var FeatureBreak = ({ geometry }) => geometry.featureCut ? /* @__PURE__ */ jsx14(
1309
1447
  "path",
1310
1448
  {
1311
1449
  d: featureBreak(SECTION_PAD.left, geometry.featureTopY, geometry.featureBottomY),
@@ -1318,9 +1456,9 @@ var FeatureDepth = ({ geometry, units }) => {
1318
1456
  const { x, featureTopY, datumX } = geometry;
1319
1457
  const { depth } = geometry.feature;
1320
1458
  if (depth === null) return null;
1321
- return /* @__PURE__ */ jsxs13("g", { children: [
1322
- /* @__PURE__ */ jsx15("title", { children: "Feature depth" }),
1323
- /* @__PURE__ */ jsx15(
1459
+ return /* @__PURE__ */ jsxs11("g", { children: [
1460
+ /* @__PURE__ */ jsx14("title", { children: "Feature depth" }),
1461
+ /* @__PURE__ */ jsx14(
1324
1462
  "line",
1325
1463
  {
1326
1464
  x1: SECTION_PAD.left,
@@ -1331,7 +1469,7 @@ var FeatureDepth = ({ geometry, units }) => {
1331
1469
  className: FEATURE_LINE
1332
1470
  }
1333
1471
  ),
1334
- /* @__PURE__ */ jsx15(
1472
+ /* @__PURE__ */ jsx14(
1335
1473
  "line",
1336
1474
  {
1337
1475
  x1: x(0),
@@ -1343,7 +1481,7 @@ var FeatureDepth = ({ geometry, units }) => {
1343
1481
  className: FEATURE_LINE
1344
1482
  }
1345
1483
  ),
1346
- x(0) - SECTION_PAD.left >= DEPTH_WORD_PX ? /* @__PURE__ */ jsx15(
1484
+ x(0) - SECTION_PAD.left >= DEPTH_WORD_PX ? /* @__PURE__ */ jsx14(
1347
1485
  "text",
1348
1486
  {
1349
1487
  x: SECTION_PAD.left + 4,
@@ -1354,7 +1492,7 @@ var FeatureDepth = ({ geometry, units }) => {
1354
1492
  children: "depth"
1355
1493
  }
1356
1494
  ) : null,
1357
- /* @__PURE__ */ jsx15(
1495
+ /* @__PURE__ */ jsx14(
1358
1496
  "text",
1359
1497
  {
1360
1498
  x: datumX + 4,
@@ -1370,8 +1508,8 @@ var FeatureDepth = ({ geometry, units }) => {
1370
1508
  var ReadRun = ({ geometry, units, arrowId }) => {
1371
1509
  const { x, last, cut, floorY, ordinateY } = geometry;
1372
1510
  if (last <= 0 || cut) return null;
1373
- return /* @__PURE__ */ jsxs13("g", { children: [
1374
- /* @__PURE__ */ jsx15(
1511
+ return /* @__PURE__ */ jsxs11("g", { children: [
1512
+ /* @__PURE__ */ jsx14(
1375
1513
  "line",
1376
1514
  {
1377
1515
  x1: x(0),
@@ -1384,7 +1522,7 @@ var ReadRun = ({ geometry, units, arrowId }) => {
1384
1522
  className: LINE
1385
1523
  }
1386
1524
  ),
1387
- /* @__PURE__ */ jsx15(
1525
+ /* @__PURE__ */ jsx14(
1388
1526
  "line",
1389
1527
  {
1390
1528
  x1: x(last),
@@ -1395,7 +1533,7 @@ var ReadRun = ({ geometry, units, arrowId }) => {
1395
1533
  className: LINE
1396
1534
  }
1397
1535
  ),
1398
- /* @__PURE__ */ jsxs13(
1536
+ /* @__PURE__ */ jsxs11(
1399
1537
  "text",
1400
1538
  {
1401
1539
  x: (x(0) + x(last)) / 2,
@@ -1414,8 +1552,8 @@ var ReadRun = ({ geometry, units, arrowId }) => {
1414
1552
  var OffsetOrdinates = (props) => {
1415
1553
  const { geometry, units } = props;
1416
1554
  const { x, y, right, width, floorY, ordinateY } = geometry;
1417
- return /* @__PURE__ */ jsxs13(Fragment4, { children: [
1418
- /* @__PURE__ */ jsx15(
1555
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1556
+ /* @__PURE__ */ jsx14(
1419
1557
  "text",
1420
1558
  {
1421
1559
  x: Math.min(
@@ -1430,7 +1568,7 @@ var OffsetOrdinates = (props) => {
1430
1568
  children: "XY PLANE DISTANCE FROM FEATURE"
1431
1569
  }
1432
1570
  ),
1433
- /* @__PURE__ */ jsx15(
1571
+ /* @__PURE__ */ jsx14(
1434
1572
  "line",
1435
1573
  {
1436
1574
  x1: x(0),
@@ -1441,9 +1579,9 @@ var OffsetOrdinates = (props) => {
1441
1579
  className: LINE
1442
1580
  }
1443
1581
  ),
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(
1582
+ /* @__PURE__ */ jsx14("text", { x: x(0), y: ordinateY + 15, textAnchor: "middle", fontSize: 10, className: TEXT, children: "0" }),
1583
+ geometry.across.map((riser) => /* @__PURE__ */ jsxs11("g", { children: [
1584
+ /* @__PURE__ */ jsx14(
1447
1585
  "line",
1448
1586
  {
1449
1587
  x1: x(riser.at),
@@ -1455,7 +1593,7 @@ var OffsetOrdinates = (props) => {
1455
1593
  className: LINE
1456
1594
  }
1457
1595
  ),
1458
- /* @__PURE__ */ jsx15(
1596
+ /* @__PURE__ */ jsx14(
1459
1597
  "text",
1460
1598
  {
1461
1599
  x: x(riser.at),
@@ -1467,15 +1605,15 @@ var OffsetOrdinates = (props) => {
1467
1605
  }
1468
1606
  )
1469
1607
  ] }, `x:${riser.at}`)),
1470
- /* @__PURE__ */ jsx15(ReadRun, { ...props })
1608
+ /* @__PURE__ */ jsx14(ReadRun, { ...props })
1471
1609
  ] });
1472
1610
  };
1473
1611
  var HeightOrdinates = ({ geometry, units }) => {
1474
1612
  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(
1613
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1614
+ /* @__PURE__ */ jsx14("text", { x: datumX + 4, y: floorY + 3.5, fontSize: 10, className: TEXT, children: "0" }),
1615
+ geometry.up.map((h) => /* @__PURE__ */ jsxs11("g", { children: [
1616
+ /* @__PURE__ */ jsx14(
1479
1617
  "line",
1480
1618
  {
1481
1619
  x1: right - 2,
@@ -1487,37 +1625,37 @@ var HeightOrdinates = ({ geometry, units }) => {
1487
1625
  className: LINE
1488
1626
  }
1489
1627
  ),
1490
- /* @__PURE__ */ jsx15("text", { x: datumX + 4, y: y(h) + 3.5, fontSize: 10, className: TEXT, children: lengthValue(h, units) })
1628
+ /* @__PURE__ */ jsx14("text", { x: datumX + 4, y: y(h) + 3.5, fontSize: 10, className: TEXT, children: lengthValue(h, units) })
1491
1629
  ] }, `y:${h}`))
1492
1630
  ] });
1493
1631
  };
1494
1632
 
1495
1633
  // src/reach/reach-section.tsx
1496
- import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
1634
+ import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
1497
1635
  var ReachSection = ({
1498
1636
  curve: whole,
1499
1637
  units,
1500
1638
  feature,
1501
1639
  width
1502
1640
  }) => {
1503
- const [hover, setHover] = useState8(null);
1641
+ const [hover, setHover] = useState9(null);
1504
1642
  const geometry = useMemo3(() => sectionGeometry(whole, feature, width), [whole, feature, width]);
1505
- const reactId = useId();
1643
+ const reactId = useId2();
1506
1644
  const arrowId = `reach-dim-arrow-${reactId.replace(/[^\w-]/g, "")}`;
1507
1645
  const reading = hover === null ? null : readingAt(whole, hover);
1508
1646
  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 })
1647
+ () => /* @__PURE__ */ jsxs12(Fragment5, { children: [
1648
+ /* @__PURE__ */ jsx15(Arrows, { id: arrowId }),
1649
+ /* @__PURE__ */ jsx15(SectionShapes, { geometry }),
1650
+ /* @__PURE__ */ jsx15(SectionHeadings, { geometry }),
1651
+ /* @__PURE__ */ jsx15(FeatureBreak, { geometry }),
1652
+ /* @__PURE__ */ jsx15(FeatureDepth, { geometry, units }),
1653
+ /* @__PURE__ */ jsx15(OffsetOrdinates, { geometry, units, arrowId }),
1654
+ /* @__PURE__ */ jsx15(HeightOrdinates, { geometry, units })
1517
1655
  ] }),
1518
1656
  [arrowId, geometry, units]
1519
1657
  );
1520
- return /* @__PURE__ */ jsxs14(
1658
+ return /* @__PURE__ */ jsxs12(
1521
1659
  "svg",
1522
1660
  {
1523
1661
  viewBox: `0 0 ${width} ${geometry.height}`,
@@ -1530,17 +1668,17 @@ var ReachSection = ({
1530
1668
  onPointerLeave: () => setHover(null),
1531
1669
  children: [
1532
1670
  drawing,
1533
- reading ? /* @__PURE__ */ jsx16(ReadingMark, { geometry, reading, units }) : null
1671
+ reading ? /* @__PURE__ */ jsx15(ReadingMark, { geometry, reading, units }) : null
1534
1672
  ]
1535
1673
  }
1536
1674
  );
1537
1675
  };
1538
1676
 
1539
1677
  // src/reach/use-element-size.ts
1540
- import { useLayoutEffect, useRef as useRef4, useState as useState9 } from "react";
1678
+ import { useLayoutEffect, useRef as useRef5, useState as useState10 } from "react";
1541
1679
  var useElementSize = () => {
1542
- const ref = useRef4(null);
1543
- const [size, setSize] = useState9({ width: 0, height: 0 });
1680
+ const ref = useRef5(null);
1681
+ const [size, setSize] = useState10({ width: 0, height: 0 });
1544
1682
  useLayoutEffect(() => {
1545
1683
  const element = ref.current;
1546
1684
  if (!element) return;
@@ -1558,12 +1696,12 @@ var useElementSize = () => {
1558
1696
  };
1559
1697
 
1560
1698
  // src/reach/reach-chart.tsx
1561
- import { Fragment as Fragment6, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
1699
+ import { Fragment as Fragment6, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1562
1700
  var CHART_MIN_W = 200;
1563
1701
  var DRAWING_MIN_W = 240;
1564
1702
  var ReachChart = ({ curve, units, feature }) => {
1565
1703
  const [ref, size] = useElementSize();
1566
- return /* @__PURE__ */ jsx17("div", { ref, className: "w-full", children: /* @__PURE__ */ jsx17(
1704
+ return /* @__PURE__ */ jsx16("div", { ref, className: "w-full", children: /* @__PURE__ */ jsx16(
1567
1705
  ReachSection,
1568
1706
  {
1569
1707
  curve,
@@ -1581,7 +1719,7 @@ var CutNote = ({
1581
1719
  if (!trimmed.cut) return null;
1582
1720
  const drawnTo = trimmed.curve.offsets[trimmed.curve.offsets.length - 1] ?? 0;
1583
1721
  const readTo = curve.offsets[curve.offsets.length - 1] ?? 0;
1584
- return /* @__PURE__ */ jsxs15(Fragment6, { children: [
1722
+ return /* @__PURE__ */ jsxs13(Fragment6, { children: [
1585
1723
  " ",
1586
1724
  "It is at full height from ",
1587
1725
  lengthValue(drawnTo, units),
@@ -1596,13 +1734,13 @@ var CutNote = ({
1596
1734
  };
1597
1735
  var ReachDrawing = ({ curve, units, feature }) => {
1598
1736
  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: [
1737
+ return /* @__PURE__ */ jsxs13("div", { ref, className: "flex flex-col gap-3 p-4", children: [
1738
+ /* @__PURE__ */ jsxs13("figure", { className: "flex flex-col gap-1", children: [
1739
+ /* @__PURE__ */ jsxs13("figcaption", { className: "text-2xs font-semibold text-gray-400 uppercase dark:text-zinc-500", children: [
1602
1740
  "Section at the wall \xB7 to scale \xB7 ",
1603
1741
  unitLabel(units)
1604
1742
  ] }),
1605
- /* @__PURE__ */ jsx17(
1743
+ /* @__PURE__ */ jsx16(
1606
1744
  ReachSection,
1607
1745
  {
1608
1746
  curve,
@@ -1612,9 +1750,9 @@ var ReachDrawing = ({ curve, units, feature }) => {
1612
1750
  }
1613
1751
  )
1614
1752
  ] }),
1615
- /* @__PURE__ */ jsxs15("p", { className: "text-2xs text-gray-400 dark:text-zinc-500", children: [
1753
+ /* @__PURE__ */ jsxs13("p", { className: "text-2xs text-gray-400 dark:text-zinc-500", children: [
1616
1754
  "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 })
1755
+ /* @__PURE__ */ jsx16(CutNote, { curve, units })
1618
1756
  ] })
1619
1757
  ] });
1620
1758
  };
@@ -1638,7 +1776,7 @@ var storageFolds = (storage, prefix) => ({
1638
1776
  });
1639
1777
 
1640
1778
  // src/feature-panel/feature-details.tsx
1641
- import { useCallback as useCallback2, useMemo as useMemo7, useState as useState12 } from "react";
1779
+ import { useCallback as useCallback2, useMemo as useMemo7, useState as useState13 } from "react";
1642
1780
  import { CrosshairIcon, InfoIcon } from "@phosphor-icons/react";
1643
1781
  import { Button as Button3, Tooltip, cn as cn12 } from "@toolpath/ui";
1644
1782
 
@@ -1655,22 +1793,22 @@ var secondaryText = (look) => look.muted === "strong" ? "text-gray dark:text-zin
1655
1793
  var faintText = (look) => look.muted === "strong" ? "text-gray dark:text-zinc-300" : "text-gray-300 dark:text-zinc-500";
1656
1794
 
1657
1795
  // 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: [
1796
+ import { Fragment as Fragment7, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1797
+ var FeatureName = ({ feature, label, look }) => /* @__PURE__ */ jsxs14(Fragment7, { children: [
1660
1798
  label ?? featureTypeLabel(feature.featureType),
1661
1799
  " ",
1662
- /* @__PURE__ */ jsxs16("span", { className: cn9("font-normal", faintText(look)), children: [
1800
+ /* @__PURE__ */ jsxs14("span", { className: cn9("font-normal", faintText(look)), children: [
1663
1801
  "from ",
1664
1802
  directionLabel(feature.machiningDirection)
1665
1803
  ] })
1666
1804
  ] });
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" });
1805
+ 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
1806
 
1669
1807
  // src/feature-panel/fold-section.tsx
1670
- import { useState as useState10 } from "react";
1808
+ import { useState as useState11 } from "react";
1671
1809
  import { CaretRightIcon as CaretRightIcon3 } from "@phosphor-icons/react";
1672
1810
  import { cn as cn10 } from "@toolpath/ui";
1673
- import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
1811
+ import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1674
1812
  var FoldSection = ({
1675
1813
  id,
1676
1814
  title: title2,
@@ -1680,10 +1818,10 @@ var FoldSection = ({
1680
1818
  className,
1681
1819
  children
1682
1820
  }) => {
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(
1821
+ const [open, setOpen] = useState11(() => folds?.read(id) ?? defaultOpen);
1822
+ return /* @__PURE__ */ jsxs15("section", { className: cn10("flex flex-col gap-1", className), children: [
1823
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-1", children: [
1824
+ /* @__PURE__ */ jsxs15(
1687
1825
  "button",
1688
1826
  {
1689
1827
  type: "button",
@@ -1698,14 +1836,14 @@ var FoldSection = ({
1698
1836
  "outline-none focus-visible:ring-2 focus-visible:ring-info/75"
1699
1837
  ),
1700
1838
  children: [
1701
- /* @__PURE__ */ jsx19(
1839
+ /* @__PURE__ */ jsx18(
1702
1840
  CaretRightIcon3,
1703
1841
  {
1704
1842
  weight: "bold",
1705
1843
  className: cn10("size-3 shrink-0 transition-transform duration-150", open && "rotate-90")
1706
1844
  }
1707
1845
  ),
1708
- /* @__PURE__ */ jsx19("span", { className: "truncate", children: title2 })
1846
+ /* @__PURE__ */ jsx18("span", { className: "truncate", children: title2 })
1709
1847
  ]
1710
1848
  }
1711
1849
  ),
@@ -1716,22 +1854,22 @@ var FoldSection = ({
1716
1854
  };
1717
1855
 
1718
1856
  // src/feature-panel/read-state.tsx
1719
- import { useEffect as useEffect3, useState as useState11 } from "react";
1857
+ import { useEffect as useEffect4, useState as useState12 } from "react";
1720
1858
  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 });
1859
+ import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
1860
+ var Reading = ({ children }) => /* @__PURE__ */ jsx19("p", { className: "text-sm text-gray-400 dark:text-zinc-400", role: "status", children });
1723
1861
  var ReadFailure = ({
1724
1862
  failure
1725
1863
  }) => {
1726
1864
  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
1865
+ return /* @__PURE__ */ jsxs16("div", { className: "flex flex-col items-start gap-2 text-sm", children: [
1866
+ /* @__PURE__ */ jsx19("p", { className: "text-danger", role: "alert", children: failure.message }),
1867
+ failure.retry ? /* @__PURE__ */ jsx19(Button2, { variant: "secondary", size: "sm", disabled: waiting, onClick: failure.retry, children: "Retry" }) : null
1730
1868
  ] });
1731
1869
  };
1732
1870
  var useWaiting = (until) => {
1733
- const [passed, setPassed] = useState11(void 0);
1734
- useEffect3(() => {
1871
+ const [passed, setPassed] = useState12(void 0);
1872
+ useEffect4(() => {
1735
1873
  if (until === void 0) return;
1736
1874
  const timer = setTimeout(() => setPassed(until), Math.max(until - Date.now(), 0));
1737
1875
  return () => clearTimeout(timer);
@@ -1751,7 +1889,7 @@ import { IconButton as IconButton3 } from "@toolpath/ui";
1751
1889
 
1752
1890
  // src/feature-panel/record-sections.tsx
1753
1891
  import { useMemo as useMemo5 } from "react";
1754
- import { CheckIcon as CheckIcon2, CopyIcon as CopyIcon2 } from "@phosphor-icons/react";
1892
+ import { CheckIcon as CheckIcon3, CopyIcon as CopyIcon2 } from "@phosphor-icons/react";
1755
1893
  import { IconButton as IconButton2, cn as cn11 } from "@toolpath/ui";
1756
1894
 
1757
1895
  // src/model/feature-record.ts
@@ -1784,7 +1922,7 @@ var datasheetFields = (datasheet) => {
1784
1922
  };
1785
1923
 
1786
1924
  // src/feature-panel/record-sections.tsx
1787
- import { Fragment as Fragment8, jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
1925
+ import { Fragment as Fragment8, jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
1788
1926
  var COPIED_MS2 = 1500;
1789
1927
  var RecordSections = ({
1790
1928
  record,
@@ -1796,10 +1934,10 @@ var RecordSections = ({
1796
1934
  const { fields, more } = useMemo5(() => datasheetFields(value?.datasheet), [value]);
1797
1935
  const json = useMemo5(() => value ? JSON.stringify(value, null, 2) : "", [value]);
1798
1936
  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;
1937
+ 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
1938
  const count = more ? `${MAX_DATASHEET_FIELDS}+` : String(fields.length);
1801
- return /* @__PURE__ */ jsxs19(Fragment8, { children: [
1802
- /* @__PURE__ */ jsx21(
1939
+ return /* @__PURE__ */ jsxs17(Fragment8, { children: [
1940
+ /* @__PURE__ */ jsx20(
1803
1941
  FoldSection,
1804
1942
  {
1805
1943
  id: "datasheet",
@@ -1807,37 +1945,37 @@ var RecordSections = ({
1807
1945
  title: value ? `All datasheet fields (${count})` : "All datasheet fields",
1808
1946
  action: value ? popOut?.("datasheet") : null,
1809
1947
  folds,
1810
- children: waiting ?? /* @__PURE__ */ jsx21(DatasheetFields, { record: value, look })
1948
+ children: waiting ?? /* @__PURE__ */ jsx20(DatasheetFields, { record: value, look })
1811
1949
  }
1812
1950
  ),
1813
- /* @__PURE__ */ jsx21(
1951
+ /* @__PURE__ */ jsx20(
1814
1952
  FoldSection,
1815
1953
  {
1816
1954
  id: "raw",
1817
1955
  defaultOpen: false,
1818
1956
  title: "Raw API record",
1819
- action: value ? /* @__PURE__ */ jsxs19(Fragment8, { children: [
1820
- /* @__PURE__ */ jsx21(CopyButton, { text: json }),
1957
+ action: value ? /* @__PURE__ */ jsxs17(Fragment8, { children: [
1958
+ /* @__PURE__ */ jsx20(CopyButton, { text: json }),
1821
1959
  popOut?.("raw")
1822
1960
  ] }) : null,
1823
1961
  folds,
1824
- children: waiting ?? /* @__PURE__ */ jsx21(RawRecord, { json })
1962
+ children: waiting ?? /* @__PURE__ */ jsx20(RawRecord, { json })
1825
1963
  }
1826
1964
  )
1827
1965
  ] });
1828
1966
  };
1829
1967
  var DatasheetFields = ({ record, look, wide = false }) => {
1830
1968
  const { fields } = useMemo5(() => datasheetFields(record?.datasheet), [record]);
1831
- return record?.datasheet ? /* @__PURE__ */ jsx21(
1969
+ return record?.datasheet ? /* @__PURE__ */ jsx20(
1832
1970
  "dl",
1833
1971
  {
1834
1972
  className: cn11(
1835
1973
  "grid grid-cols-[minmax(0,1fr)_auto] gap-y-0.5 text-xs",
1836
1974
  wide ? "gap-x-4 p-4" : "gap-x-3"
1837
1975
  ),
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(
1976
+ children: fields.map((field) => /* @__PURE__ */ jsxs17("div", { className: "contents", children: [
1977
+ /* @__PURE__ */ jsx20("dt", { className: cn11("truncate font-mono", secondaryText(look)), title: field.path, children: field.path }),
1978
+ /* @__PURE__ */ jsx20(
1841
1979
  "dd",
1842
1980
  {
1843
1981
  className: cn11(
@@ -1850,12 +1988,12 @@ var DatasheetFields = ({ record, look, wide = false }) => {
1850
1988
  )
1851
1989
  ] }, field.path))
1852
1990
  }
1853
- ) : /* @__PURE__ */ jsx21("p", { className: cn11("text-sm", secondaryText(look), wide && "p-4"), children: "The API sent no datasheet for this feature." });
1991
+ ) : /* @__PURE__ */ jsx20("p", { className: cn11("text-sm", secondaryText(look), wide && "p-4"), children: "The API sent no datasheet for this feature." });
1854
1992
  };
1855
1993
  var RawRecord = ({
1856
1994
  json,
1857
1995
  wide = false
1858
- }) => /* @__PURE__ */ jsx21(
1996
+ }) => /* @__PURE__ */ jsx20(
1859
1997
  "pre",
1860
1998
  {
1861
1999
  className: cn11(
@@ -1867,7 +2005,7 @@ var RawRecord = ({
1867
2005
  );
1868
2006
  var CopyButton = ({ text }) => {
1869
2007
  const [copied, setCopied] = useTimedValue(COPIED_MS2);
1870
- return /* @__PURE__ */ jsx21(
2008
+ return /* @__PURE__ */ jsx20(
1871
2009
  IconButton2,
1872
2010
  {
1873
2011
  "aria-label": copied ? "Copied" : "Copy raw record",
@@ -1877,13 +2015,13 @@ var CopyButton = ({ text }) => {
1877
2015
  onClick: () => {
1878
2016
  void navigator.clipboard?.writeText(text).then(() => setCopied(true)).catch(() => void 0);
1879
2017
  },
1880
- children: copied ? /* @__PURE__ */ jsx21(CheckIcon2, { weight: "bold" }) : /* @__PURE__ */ jsx21(CopyIcon2, { weight: "bold" })
2018
+ children: copied ? /* @__PURE__ */ jsx20(CheckIcon3, { weight: "bold" }) : /* @__PURE__ */ jsx20(CopyIcon2, { weight: "bold" })
1881
2019
  }
1882
2020
  );
1883
2021
  };
1884
2022
 
1885
2023
  // src/feature-panel/pop-outs.tsx
1886
- import { Fragment as Fragment9, jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
2024
+ import { Fragment as Fragment9, jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
1887
2025
  var TITLES = {
1888
2026
  reach: "Reach",
1889
2027
  datasheet: "All datasheet fields",
@@ -1897,7 +2035,7 @@ var LABELS = {
1897
2035
  var PopOutButton = ({
1898
2036
  id,
1899
2037
  onOpen
1900
- }) => /* @__PURE__ */ jsx22(
2038
+ }) => /* @__PURE__ */ jsx21(
1901
2039
  IconButton3,
1902
2040
  {
1903
2041
  "aria-label": LABELS[id],
@@ -1905,7 +2043,7 @@ var PopOutButton = ({
1905
2043
  variant: "muted",
1906
2044
  size: "sm",
1907
2045
  onClick: () => onOpen(id),
1908
- children: /* @__PURE__ */ jsx22(ArrowSquareOutIcon, { weight: "bold" })
2046
+ children: /* @__PURE__ */ jsx21(ArrowSquareOutIcon, { weight: "bold" })
1909
2047
  }
1910
2048
  );
1911
2049
  var PopOuts = ({
@@ -1921,46 +2059,46 @@ var PopOuts = ({
1921
2059
  const curve = useMemo6(() => readReachCurve(record?.datasheet), [record]);
1922
2060
  const json = useMemo6(() => record ? JSON.stringify(record, null, 2) : "", [record]);
1923
2061
  if (!record) return null;
1924
- return /* @__PURE__ */ jsxs20(Fragment9, { children: [
1925
- open.has("reach") && curve ? /* @__PURE__ */ jsx22(
2062
+ return /* @__PURE__ */ jsxs18(Fragment9, { children: [
2063
+ open.has("reach") && curve ? /* @__PURE__ */ jsx21(
1926
2064
  Window,
1927
2065
  {
1928
2066
  id: "reach",
1929
2067
  title: TITLES.reach,
1930
2068
  subtitle,
1931
- icon: /* @__PURE__ */ jsx22(ChartLineIcon, { weight: "bold" }),
2069
+ icon: /* @__PURE__ */ jsx21(ChartLineIcon, { weight: "bold" }),
1932
2070
  onClose: () => onClose("reach"),
1933
- children: /* @__PURE__ */ jsx22(ReachDrawing, { curve, units, feature: profile })
2071
+ children: /* @__PURE__ */ jsx21(ReachDrawing, { curve, units, feature: profile })
1934
2072
  }
1935
2073
  ) : null,
1936
- open.has("datasheet") ? /* @__PURE__ */ jsx22(
2074
+ open.has("datasheet") ? /* @__PURE__ */ jsx21(
1937
2075
  Window,
1938
2076
  {
1939
2077
  id: "datasheet",
1940
2078
  title: TITLES.datasheet,
1941
2079
  subtitle,
1942
- icon: /* @__PURE__ */ jsx22(TableIcon, { weight: "bold" }),
2080
+ icon: /* @__PURE__ */ jsx21(TableIcon, { weight: "bold" }),
1943
2081
  onClose: () => onClose("datasheet"),
1944
- children: /* @__PURE__ */ jsx22(DatasheetFields, { record, look, wide: true })
2082
+ children: /* @__PURE__ */ jsx21(DatasheetFields, { record, look, wide: true })
1945
2083
  }
1946
2084
  ) : null,
1947
- open.has("raw") ? /* @__PURE__ */ jsx22(
2085
+ open.has("raw") ? /* @__PURE__ */ jsx21(
1948
2086
  Window,
1949
2087
  {
1950
2088
  id: "raw",
1951
2089
  title: TITLES.raw,
1952
2090
  subtitle,
1953
- icon: /* @__PURE__ */ jsx22(BracketsCurlyIcon, { weight: "bold" }),
1954
- action: /* @__PURE__ */ jsx22(CopyButton, { text: json }),
2091
+ icon: /* @__PURE__ */ jsx21(BracketsCurlyIcon, { weight: "bold" }),
2092
+ action: /* @__PURE__ */ jsx21(CopyButton, { text: json }),
1955
2093
  onClose: () => onClose("raw"),
1956
- children: /* @__PURE__ */ jsx22(RawRecord, { json, wide: true })
2094
+ children: /* @__PURE__ */ jsx21(RawRecord, { json, wide: true })
1957
2095
  }
1958
2096
  ) : null
1959
2097
  ] });
1960
2098
  };
1961
2099
 
1962
2100
  // src/feature-panel/feature-details.tsx
1963
- import { Fragment as Fragment10, jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
2101
+ import { Fragment as Fragment10, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
1964
2102
  var FeatureDetails = ({
1965
2103
  feature,
1966
2104
  units,
@@ -1980,8 +2118,8 @@ var FeatureDetails = ({
1980
2118
  folds
1981
2119
  }) => {
1982
2120
  const look = resolveLook(lookOption);
1983
- const name = /* @__PURE__ */ jsx23(FeatureName, { feature, label, look });
1984
- const [out, setOut] = useState12(/* @__PURE__ */ new Set());
2121
+ const name = /* @__PURE__ */ jsx22(FeatureName, { feature, label, look });
2122
+ const [out, setOut] = useState13(/* @__PURE__ */ new Set());
1985
2123
  const openOut = useCallback2((id) => setOut((was) => new Set(was).add(id)), []);
1986
2124
  const closeOut = useCallback2(
1987
2125
  (id) => setOut((was) => {
@@ -1991,31 +2129,31 @@ var FeatureDetails = ({
1991
2129
  }),
1992
2130
  []
1993
2131
  );
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" }),
2132
+ const popOut = PopOut ? (id) => /* @__PURE__ */ jsx22(PopOutButton, { id, onOpen: openOut }) : void 0;
2133
+ return /* @__PURE__ */ jsxs19("div", { className: "flex flex-col gap-4 p-4", children: [
2134
+ /* @__PURE__ */ jsxs19("div", { className: "flex flex-col gap-2", children: [
2135
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-start gap-2", children: [
2136
+ /* @__PURE__ */ jsxs19("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
2137
+ /* @__PURE__ */ jsx22("h3", { className: "truncate text-sm font-semibold text-gray-600 dark:text-zinc-100", children: name }),
2138
+ /* @__PURE__ */ jsxs19("p", { className: cn12("flex flex-wrap items-center gap-1.5 text-xs", secondaryText(look)), children: [
2139
+ /* @__PURE__ */ jsx22(Swatch, { color: directionColor, className: "size-2.5" }),
2002
2140
  "Machined from ",
2003
2141
  directionLabel(feature.machiningDirection),
2004
- required ? /* @__PURE__ */ jsx23(RequiredBadge, {}) : null,
2142
+ required ? /* @__PURE__ */ jsx22(RequiredBadge, {}) : null,
2005
2143
  status
2006
2144
  ] })
2007
2145
  ] }),
2008
2146
  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" }),
2147
+ 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: [
2148
+ /* @__PURE__ */ jsx22(CrosshairIcon, { weight: "bold", className: "size-3.5" }),
2011
2149
  "Show this feature"
2012
2150
  ] }) }) : null
2013
2151
  ] }),
2014
2152
  children
2015
2153
  ] }),
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(
2154
+ /* @__PURE__ */ jsx22(FoldSection, { id: "rules", title: "Rules broken", folds, children: /* @__PURE__ */ jsx22(BrokenRuleList, { rules, look }) }),
2155
+ /* @__PURE__ */ jsx22(MeasurementSections, { measurements, look, folds }),
2156
+ /* @__PURE__ */ jsx22(
2019
2157
  ReachSection2,
2020
2158
  {
2021
2159
  record,
@@ -2025,8 +2163,8 @@ var FeatureDetails = ({
2025
2163
  action: popOut?.("reach")
2026
2164
  }
2027
2165
  ),
2028
- /* @__PURE__ */ jsx23(RecordSections, { record, look, folds, popOut }),
2029
- PopOut ? /* @__PURE__ */ jsx23(
2166
+ /* @__PURE__ */ jsx22(RecordSections, { record, look, folds, popOut }),
2167
+ PopOut ? /* @__PURE__ */ jsx22(
2030
2168
  PopOuts,
2031
2169
  {
2032
2170
  open: out,
@@ -2044,14 +2182,14 @@ var FeatureDetails = ({
2044
2182
  var BrokenRuleList = ({
2045
2183
  rules,
2046
2184
  look
2047
- }) => rules.length > 0 ? /* @__PURE__ */ jsx23("ul", { className: "flex flex-col gap-1", children: rules.map((rule) => /* @__PURE__ */ jsxs21(
2185
+ }) => rules.length > 0 ? /* @__PURE__ */ jsx22("ul", { className: "flex flex-col gap-1", children: rules.map((rule) => /* @__PURE__ */ jsxs19(
2048
2186
  "li",
2049
2187
  {
2050
2188
  className: "flex items-start gap-2 text-sm text-gray-600 dark:text-zinc-100",
2051
2189
  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(
2190
+ /* @__PURE__ */ jsx22(Swatch, { color: rule.color, className: "mt-0.5" }),
2191
+ /* @__PURE__ */ jsx22(Explained, { note: rule.note, children: /* @__PURE__ */ jsx22("span", { className: "min-w-0 flex-1", children: rule.text }) }),
2192
+ rule.figure ? /* @__PURE__ */ jsxs19(
2055
2193
  "span",
2056
2194
  {
2057
2195
  className: cn12(
@@ -2061,15 +2199,15 @@ var BrokenRuleList = ({
2061
2199
  style: look.rules === "result" ? { color: rule.color } : void 0,
2062
2200
  children: [
2063
2201
  rule.figure,
2064
- look.rules === "result" && rule.limit ? /* @__PURE__ */ jsx23("span", { className: "ml-1 font-normal opacity-60", children: rule.limit }) : null
2202
+ look.rules === "result" && rule.limit ? /* @__PURE__ */ jsx22("span", { className: "ml-1 font-normal opacity-60", children: rule.limit }) : null
2065
2203
  ]
2066
2204
  }
2067
2205
  ) : null
2068
2206
  ]
2069
2207
  },
2070
2208
  rule.key
2071
- )) }) : /* @__PURE__ */ jsx23(None, { look });
2072
- var Explained = ({ note, children }) => note ? /* @__PURE__ */ jsx23(Tooltip, { tip: note, side: "left", children: /* @__PURE__ */ jsx23(
2209
+ )) }) : /* @__PURE__ */ jsx22(None, { look });
2210
+ var Explained = ({ note, children }) => note ? /* @__PURE__ */ jsx22(Tooltip, { tip: note, side: "left", children: /* @__PURE__ */ jsx22(
2073
2211
  "span",
2074
2212
  {
2075
2213
  tabIndex: 0,
@@ -2077,35 +2215,35 @@ var Explained = ({ note, children }) => note ? /* @__PURE__ */ jsx23(Tooltip, {
2077
2215
  children
2078
2216
  }
2079
2217
  ) }) : children;
2080
- var None = ({ look }) => /* @__PURE__ */ jsx23("p", { className: cn12("text-sm", secondaryText(look)), children: "None" });
2218
+ var None = ({ look }) => /* @__PURE__ */ jsx22("p", { className: cn12("text-sm", secondaryText(look)), children: "None" });
2081
2219
  var MeasurementSections = ({ measurements, look, folds }) => {
2082
2220
  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 }) }) });
2221
+ 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
2222
  }
2085
2223
  const measured = measurements.value.filter((row) => !row.milling);
2086
2224
  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
2225
+ return /* @__PURE__ */ jsxs19(Fragment10, { children: [
2226
+ /* @__PURE__ */ jsx22(FoldSection, { id: "measurements", title: "Measurements", folds, children: /* @__PURE__ */ jsx22(MeasurementList, { rows: measured, look }) }),
2227
+ milling.length > 0 ? /* @__PURE__ */ jsx22(FoldSection, { id: "milling", title: "Milling considerations", folds, children: /* @__PURE__ */ jsx22(MeasurementList, { rows: milling, look }) }) : null
2090
2228
  ] });
2091
2229
  };
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: [
2230
+ 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: [
2231
+ /* @__PURE__ */ jsxs19("dt", { className: cn12("flex items-center gap-1", secondaryText(look)), children: [
2094
2232
  row.label,
2095
- /* @__PURE__ */ jsx23(Derivation, { lines: row.derivation, look })
2233
+ /* @__PURE__ */ jsx22(Derivation, { lines: row.derivation, look })
2096
2234
  ] }),
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 });
2235
+ /* @__PURE__ */ jsx22("dd", { className: "text-right text-gray-600 tabular-nums dark:text-zinc-100", title: row.alt, children: row.value })
2236
+ ] }, row.key)) }) : /* @__PURE__ */ jsx22(None, { look });
2099
2237
  var Derivation = ({
2100
2238
  lines,
2101
2239
  look
2102
- }) => lines.length > 0 ? /* @__PURE__ */ jsx23(
2240
+ }) => lines.length > 0 ? /* @__PURE__ */ jsx22(
2103
2241
  Tooltip,
2104
2242
  {
2105
2243
  side: "left",
2106
2244
  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(
2245
+ 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)) }),
2246
+ children: /* @__PURE__ */ jsx22(
2109
2247
  "button",
2110
2248
  {
2111
2249
  type: "button",
@@ -2114,7 +2252,7 @@ var Derivation = ({
2114
2252
  "shrink-0 rounded outline-none hover:text-gray-500 focus-visible:ring-2 focus-visible:ring-info/75 dark:hover:text-zinc-300",
2115
2253
  faintText(look)
2116
2254
  ),
2117
- children: /* @__PURE__ */ jsx23(InfoIcon, { weight: "bold", className: "size-3" })
2255
+ children: /* @__PURE__ */ jsx22(InfoIcon, { weight: "bold", className: "size-3" })
2118
2256
  }
2119
2257
  )
2120
2258
  }
@@ -2125,19 +2263,19 @@ var ReachSection2 = ({ record, units, profile, folds, action }) => {
2125
2263
  [record]
2126
2264
  );
2127
2265
  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 });
2266
+ 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
2267
  };
2130
2268
 
2131
2269
  // src/feature-panel/candidate-list.tsx
2132
2270
  import { XIcon } from "@phosphor-icons/react";
2133
2271
  import { IconButton as IconButton4, cn as cn13 } from "@toolpath/ui";
2134
- import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
2272
+ import { jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
2135
2273
  var candidateListKeys = listKeys("candidates");
2136
2274
  var faceLetter = (index) => String.fromCharCode(65 + index % 26);
2137
2275
  var FaceLetter = ({
2138
2276
  index,
2139
2277
  active
2140
- }) => /* @__PURE__ */ jsx24(
2278
+ }) => /* @__PURE__ */ jsx23(
2141
2279
  "span",
2142
2280
  {
2143
2281
  className: cn13(
@@ -2158,27 +2296,27 @@ var CandidateList = ({
2158
2296
  }) => {
2159
2297
  const look = resolveLook(lookOption);
2160
2298
  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(
2299
+ return /* @__PURE__ */ jsx23("div", { className: "border-b border-gray-100 p-2 dark:border-zinc-800", children: /* @__PURE__ */ jsx23(
2162
2300
  FoldSection,
2163
2301
  {
2164
2302
  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 }),
2303
+ title: face ? /* @__PURE__ */ jsxs20("span", { className: "flex items-center gap-1.5", children: [
2304
+ /* @__PURE__ */ jsx23(FaceLetter, { index: face.index, active: face.active }),
2167
2305
  count
2168
2306
  ] }) : count,
2169
- action: face?.onStop ? /* @__PURE__ */ jsx24(
2307
+ action: face?.onStop ? /* @__PURE__ */ jsx23(
2170
2308
  IconButton4,
2171
2309
  {
2172
2310
  "aria-label": `Stop comparing face ${faceLetter(face.index)}`,
2173
2311
  variant: "muted",
2174
2312
  size: "sm",
2175
2313
  onClick: face.onStop,
2176
- children: /* @__PURE__ */ jsx24(XIcon, { weight: "bold" })
2314
+ children: /* @__PURE__ */ jsx23(XIcon, { weight: "bold" })
2177
2315
  }
2178
2316
  ) : null,
2179
2317
  folds,
2180
2318
  className: "px-2",
2181
- children: /* @__PURE__ */ jsx24("ul", { className: "-mx-2 flex flex-col gap-0.5", children: rows.map((row) => /* @__PURE__ */ jsx24(
2319
+ children: /* @__PURE__ */ jsx23("ul", { className: "-mx-2 flex flex-col gap-0.5", children: rows.map((row) => /* @__PURE__ */ jsx23(
2182
2320
  Row,
2183
2321
  {
2184
2322
  row,
@@ -2195,7 +2333,7 @@ var CandidateList = ({
2195
2333
  var Row = ({ row, chosen, onSelect, onHover, look }) => {
2196
2334
  const { feature } = row;
2197
2335
  const info = look.selected === "info";
2198
- return /* @__PURE__ */ jsxs22(
2336
+ return /* @__PURE__ */ jsxs20(
2199
2337
  "li",
2200
2338
  {
2201
2339
  className: cn13(
@@ -2204,7 +2342,7 @@ var Row = ({ row, chosen, onSelect, onHover, look }) => {
2204
2342
  info && (chosen ? "border-info/60 bg-info/15" : "border-transparent")
2205
2343
  ),
2206
2344
  children: [
2207
- /* @__PURE__ */ jsxs22(
2345
+ /* @__PURE__ */ jsxs20(
2208
2346
  "button",
2209
2347
  {
2210
2348
  type: "button",
@@ -2222,18 +2360,18 @@ var Row = ({ row, chosen, onSelect, onHover, look }) => {
2222
2360
  chosen && (info ? "font-medium" : "bg-gray-100 font-semibold dark:bg-zinc-800")
2223
2361
  ),
2224
2362
  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: [
2363
+ /* @__PURE__ */ jsxs20("span", { className: "flex w-full items-center gap-2", children: [
2364
+ row.color ? /* @__PURE__ */ jsx23(Swatch, { color: row.color }) : /* @__PURE__ */ jsx23("span", { "aria-hidden": true, className: "size-4 shrink-0" }),
2365
+ /* @__PURE__ */ jsx23("span", { className: "min-w-0 flex-1 truncate", children: /* @__PURE__ */ jsx23(FeatureName, { feature, label: row.label, look }) }),
2366
+ row.ruleCount ? /* @__PURE__ */ jsxs20("span", { className: cn13("text-2xs tabular-nums", secondaryText(look)), children: [
2229
2367
  row.ruleCount,
2230
2368
  " ",
2231
2369
  row.ruleCount === 1 ? "rule" : "rules"
2232
2370
  ] }) : 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
2371
+ row.required ? /* @__PURE__ */ jsx23(RequiredBadge, {}) : null,
2372
+ row.detail ? /* @__PURE__ */ jsx23("span", { className: cn13("max-w-32 shrink-0 truncate text-xs", secondaryText(look)), children: row.detail }) : null
2235
2373
  ] }),
2236
- row.note ? /* @__PURE__ */ jsx24("span", { className: "pl-6 text-2xs text-warning-darken", children: row.note }) : null
2374
+ row.note ? /* @__PURE__ */ jsx23("span", { className: "pl-6 text-2xs text-warning-darken", children: row.note }) : null
2237
2375
  ]
2238
2376
  }
2239
2377
  ),
@@ -2246,7 +2384,7 @@ var Row = ({ row, chosen, onSelect, onHover, look }) => {
2246
2384
  // src/feature-panel/comparison-table.tsx
2247
2385
  import { useMemo as useMemo8 } from "react";
2248
2386
  import { cn as cn14 } from "@toolpath/ui";
2249
- import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
2387
+ import { jsx as jsx24, jsxs as jsxs21 } from "react/jsx-runtime";
2250
2388
  var DASH = "\u2014";
2251
2389
  var ComparisonTable = ({
2252
2390
  faces,
@@ -2274,8 +2412,8 @@ var ComparisonTable = ({
2274
2412
  const text = directionLabel(feature.machiningDirection);
2275
2413
  return {
2276
2414
  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" }),
2415
+ node: /* @__PURE__ */ jsxs21("span", { className: "inline-flex items-center gap-1", children: [
2416
+ /* @__PURE__ */ jsx24(Swatch, { color: face.directionColor, className: "size-2.5" }),
2279
2417
  text
2280
2418
  ] })
2281
2419
  };
@@ -2283,14 +2421,14 @@ var ComparisonTable = ({
2283
2421
  ...faces.some((face) => face.required !== void 0) ? [row("required", "Required", (face) => ({ text: face.required ? "Yes" : "No" }))] : [],
2284
2422
  row("rules", "Rules broken", (face) => ({
2285
2423
  text: face.rules.map((rule) => rule.key).join(",") || "None",
2286
- node: face.rules.length > 0 ? /* @__PURE__ */ jsxs23(
2424
+ node: face.rules.length > 0 ? /* @__PURE__ */ jsxs21(
2287
2425
  "span",
2288
2426
  {
2289
2427
  className: "inline-flex items-center gap-0.5",
2290
2428
  title: face.rules.map((rule) => rule.text).join("\n"),
2291
2429
  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 })
2430
+ face.rules.map((rule) => /* @__PURE__ */ jsx24(Swatch, { color: rule.color, className: "size-2.5" }, rule.key)),
2431
+ /* @__PURE__ */ jsx24("span", { className: "ml-0.5", children: face.rules.length })
2294
2432
  ]
2295
2433
  }
2296
2434
  ) : "None"
@@ -2304,16 +2442,16 @@ var ComparisonTable = ({
2304
2442
  return {
2305
2443
  key,
2306
2444
  label,
2307
- cells: values.map((value) => value ? /* @__PURE__ */ jsx25("span", { title: value.alt, children: value.value }) : DASH),
2445
+ cells: values.map((value) => value ? /* @__PURE__ */ jsx24("span", { title: value.alt, children: value.value }) : DASH),
2308
2446
  differs: new Set(values.map((value) => value?.value ?? DASH)).size > 1
2309
2447
  };
2310
2448
  });
2311
2449
  return [...fixed, ...measured];
2312
2450
  }, [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(
2451
+ 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: [
2452
+ /* @__PURE__ */ jsx24("thead", { children: /* @__PURE__ */ jsxs21("tr", { children: [
2453
+ /* @__PURE__ */ jsx24("th", { className: "w-0" }),
2454
+ faces.map(({ feature }, index) => /* @__PURE__ */ jsx24("th", { className: "text-left align-bottom font-normal", children: /* @__PURE__ */ jsxs21(
2317
2455
  "button",
2318
2456
  {
2319
2457
  type: "button",
@@ -2328,14 +2466,14 @@ var ComparisonTable = ({
2328
2466
  index === active && "bg-gray-100 font-semibold dark:bg-zinc-800"
2329
2467
  ),
2330
2468
  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" })
2469
+ /* @__PURE__ */ jsx24(FaceLetter, { index, active: index === active }),
2470
+ /* @__PURE__ */ jsx24("span", { className: "min-w-0 truncate", children: feature ? /* @__PURE__ */ jsx24(FeatureName, { feature, look }) : "No feature" })
2333
2471
  ]
2334
2472
  }
2335
2473
  ) }, index))
2336
2474
  ] }) }),
2337
- /* @__PURE__ */ jsx25("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs23("tr", { "data-differs": row.differs || void 0, children: [
2338
- /* @__PURE__ */ jsx25(
2475
+ /* @__PURE__ */ jsx24("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs21("tr", { "data-differs": row.differs || void 0, children: [
2476
+ /* @__PURE__ */ jsx24(
2339
2477
  "th",
2340
2478
  {
2341
2479
  scope: "row",
@@ -2346,7 +2484,7 @@ var ComparisonTable = ({
2346
2484
  children: row.label
2347
2485
  }
2348
2486
  ),
2349
- row.cells.map((cell, index) => /* @__PURE__ */ jsx25(
2487
+ row.cells.map((cell, index) => /* @__PURE__ */ jsx24(
2350
2488
  "td",
2351
2489
  {
2352
2490
  className: cn14(
@@ -2364,7 +2502,7 @@ var ComparisonTable = ({
2364
2502
  // src/feature-panel/feature-panel.tsx
2365
2503
  import { CubeIcon, XIcon as XIcon2 } from "@phosphor-icons/react";
2366
2504
  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";
2505
+ import { Fragment as Fragment11, jsx as jsx25, jsxs as jsxs22 } from "react/jsx-runtime";
2368
2506
  var title = (faces, active, group) => {
2369
2507
  if (faces.length > 1) return `Comparing ${faces.length} faces`;
2370
2508
  if (faces[active]?.region !== null && faces[active]?.region !== void 0)
@@ -2391,7 +2529,7 @@ var FeaturePanel = ({
2391
2529
  const comparing = faces.length > 1;
2392
2530
  const face = faces[active];
2393
2531
  const heading = title(faces, active, group);
2394
- return /* @__PURE__ */ jsxs24(
2532
+ return /* @__PURE__ */ jsxs22(
2395
2533
  "section",
2396
2534
  {
2397
2535
  "aria-label": heading,
@@ -2407,7 +2545,7 @@ var FeaturePanel = ({
2407
2545
  className
2408
2546
  ),
2409
2547
  children: [
2410
- /* @__PURE__ */ jsxs24(
2548
+ /* @__PURE__ */ jsxs22(
2411
2549
  "header",
2412
2550
  {
2413
2551
  ...headerProps,
@@ -2416,15 +2554,15 @@ var FeaturePanel = ({
2416
2554
  headerProps?.className
2417
2555
  ),
2418
2556
  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" }) })
2557
+ /* @__PURE__ */ jsx25(CubeIcon, { weight: "bold", className: cn15("size-5 shrink-0", secondaryText(look)) }),
2558
+ /* @__PURE__ */ jsx25("h2", { className: "flex-1 text-sm font-semibold text-gray-600 dark:text-zinc-100", children: heading }),
2559
+ /* @__PURE__ */ jsx25(IconButton5, { "aria-label": "Close", variant: "muted", onClick: onClose, children: /* @__PURE__ */ jsx25(XIcon2, { weight: "bold" }) })
2422
2560
  ]
2423
2561
  }
2424
2562
  ),
2425
- /* @__PURE__ */ jsxs24(ScrollArea2, { className: "min-h-0 flex-1", children: [
2426
- comparing ? /* @__PURE__ */ jsxs24(Fragment11, { children: [
2427
- faces.map((each, index) => /* @__PURE__ */ jsx26(
2563
+ /* @__PURE__ */ jsxs22(ScrollArea2, { className: "min-h-0 flex-1", children: [
2564
+ comparing ? /* @__PURE__ */ jsxs22(Fragment11, { children: [
2565
+ faces.map((each, index) => /* @__PURE__ */ jsx25(
2428
2566
  CandidateList,
2429
2567
  {
2430
2568
  rows: each.rows,
@@ -2441,7 +2579,7 @@ var FeaturePanel = ({
2441
2579
  },
2442
2580
  `${each.region ?? "list"}-${index}`
2443
2581
  )),
2444
- comparison ? /* @__PURE__ */ jsx26(
2582
+ comparison ? /* @__PURE__ */ jsx25(
2445
2583
  ComparisonTable,
2446
2584
  {
2447
2585
  faces: comparison,
@@ -2452,7 +2590,7 @@ var FeaturePanel = ({
2452
2590
  folds
2453
2591
  }
2454
2592
  ) : null
2455
- ] }) : face && face.region !== null ? /* @__PURE__ */ jsx26(
2593
+ ] }) : face && face.region !== null ? /* @__PURE__ */ jsx25(
2456
2594
  CandidateList,
2457
2595
  {
2458
2596
  rows: face.rows,
@@ -2463,7 +2601,7 @@ var FeaturePanel = ({
2463
2601
  folds
2464
2602
  }
2465
2603
  ) : 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." })
2604
+ 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
2605
  ] })
2468
2606
  ]
2469
2607
  }