partforge 0.125.0 → 0.126.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -108,6 +108,7 @@ const runtime = mount(part, {
108
108
  },
109
109
  onBuild: ({ status, ms, error }) => {}, // per accepted build: "success" | "error"
110
110
  onPick: ({ selection, label, prompt, token }) => {}, // programmatic click-to-select
111
+ pickHint: "Click to edit", // hover-tooltip nudge while onPick is set (null = none)
111
112
  });
112
113
  await runtime.ready; // first successful build (rejects on a first-build error)
113
114
  runtime.setHostPane("rail"); // narrow layout only: show just the controls
@@ -221,7 +222,10 @@ full contract.
221
222
  back to the sub-part label/name) for compact UI, `prompt` is the LLM-ready
222
223
  sentence, `token` the compact form, `selection` the raw object. When `onPick` is
223
224
  set, the `?pick` / `?pickserver` URL modes are ignored (one click listener ever
224
- live); hover labels stay always-on.
225
+ live); hover labels stay always-on, and gain a `pickHint` line under the name
226
+ ("Click to edit" by default; pass your own wording, or `null` for none). Hover
227
+ never tints the surface under the pointer — a per-face highlight read as "only
228
+ this spot is clickable".
225
229
 
226
230
  ## Authoring guide
227
231
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "partforge",
3
- "version": "0.125.0",
3
+ "version": "0.126.0",
4
4
  "description": "Turn a declarative part definition into a parametric-CAD web app (three.js + Manifold/Replicad). Requires a Vite-based consumer.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1039,6 +1039,10 @@ button.action:focus-visible, .adv-toggle:focus-visible, .sec-title:focus-visible
1039
1039
  .pf-hover-tip .pf-hover-sub { color: var(--pf-muted); font-size: 11px; margin-left: 7px; }
1040
1040
  #pf-hover-tip .pf-hover-sub:empty,
1041
1041
  .pf-hover-tip .pf-hover-sub:empty { display: none; }
1042
+ #pf-hover-tip .pf-hover-hint,
1043
+ .pf-hover-tip .pf-hover-hint { color: var(--pf-muted); font-size: 10.5px; margin-top: 1px; }
1044
+ #pf-hover-tip .pf-hover-hint:empty,
1045
+ .pf-hover-tip .pf-hover-hint:empty { display: none; }
1042
1046
 
1043
1047
  /* request-a-pick: agent prompt banner, floated top-centre well below the part tabs,
1044
1048
  laid out like a chat message (avatar + text). Slides/fades in when shown; themed. */
@@ -419,7 +419,7 @@ function createCleanupStack() {
419
419
  // Every `elements` entry defaults to the legacy global-ID lookup (below), resolved
420
420
  // exactly once here — submodules take element refs and never query the document.
421
421
  // `container`/`controls` remain as deprecated aliases for elements.viewer/.controls.
422
- export function mount(part, { createWorker, elements = {}, onBuild, onPick, onDownload, onViewChange, onParamsCommit, onAnnotationSend,
422
+ export function mount(part, { createWorker, elements = {}, onBuild, onPick, pickHint = "Click to edit", onDownload, onViewChange, onParamsCommit, onAnnotationSend,
423
423
  fontCatalog,
424
424
  imageCatalog,
425
425
  onAssetUpload,
@@ -497,7 +497,10 @@ export function mount(part, { createWorker, elements = {}, onBuild, onPick, onDo
497
497
  onRailLayout: () => railChrome.layoutChanged?.(),
498
498
  });
499
499
  cleanup.defer(() => paneTabs.detach());
500
- const hover = attachHoverLabels(viewer, { part, tooltip }); // always-on hover inspection (no-op on touch-only devices)
500
+ // Always-on hover inspection (no-op on touch-only devices). With onPick the
501
+ // host acts on a click, so the tooltip nudges toward it with `pickHint`
502
+ // (a host passes its own wording, or null for none).
503
+ const hover = attachHoverLabels(viewer, { part, tooltip, hint: onPick ? pickHint : null });
501
504
  cleanup.defer(() => hover.detach());
502
505
  const ui = createStatusUi({ ...els.status, exports: [els.exports.stl, els.exports.step, els.exports.threeMf] });
503
506
  cleanup.defer(() => ui.setStatus(""));
@@ -1,15 +1,19 @@
1
1
  // Always-on hover inspection: a cursor-following tooltip naming the feature +
2
- // sub-part under the pointer, and an overlay mesh highlighting the feature's
3
- // surface. Feature names come from Solid.label() in the part's build, carried
4
- // per-triangle in the mesh payload (geometry.userData.featureIds/features).
2
+ // sub-part under the pointer. Feature names come from Solid.label() in the
3
+ // part's build, carried per-triangle in the mesh payload
4
+ // (geometry.userData.featureIds/features).
5
+ //
6
+ // There is deliberately NO surface highlight: tinting just the feature under
7
+ // the pointer read as "only this face is clickable", and people stopped
8
+ // clicking elsewhere on the part. `hint` (e.g. "Click to edit") adds a line
9
+ // under the name when a click does something — mount passes it with onPick.
5
10
  import { createTooltipPresenter } from "../tooltip.js";
6
11
  import { raycastViewer } from "./raycast.js";
7
- import { createFeatureHighlight } from "./feature-highlight.js";
8
12
  import { runCleanupSteps } from "../teardown.js";
9
13
 
10
14
  export function attachHoverLabels(
11
15
  viewer,
12
- { part, schedule = (cb) => requestAnimationFrame(cb), tooltip } = {},
16
+ { part, schedule = (cb) => requestAnimationFrame(cb), tooltip, hint = null } = {},
13
17
  ) {
14
18
  // Hover is a mouse idiom — skip entirely on touch-only devices. The stub
15
19
  // still answers the WHOLE interface: mount.js calls setSuppressed from the
@@ -25,8 +29,6 @@ export function attachHoverLabels(
25
29
  let presentationToken;
26
30
  let hasPresented = false;
27
31
 
28
- const highlight = createFeatureHighlight(viewer);
29
-
30
32
  const subLabel = (name) => part.parts[name]?.label ?? name;
31
33
 
32
34
  function hide() {
@@ -35,7 +37,6 @@ export function attachHoverLabels(
35
37
  tooltipPresenter.hide(presentationToken);
36
38
  presentationToken = undefined;
37
39
  }
38
- highlight.clear();
39
40
  }
40
41
 
41
42
  function show(hit, x, y) {
@@ -45,7 +46,7 @@ export function attachHoverLabels(
45
46
  } else {
46
47
  content = { title: subLabel(hit.subPart), subtitle: "" };
47
48
  }
48
- highlight.show(hit);
49
+ if (hint) content.hint = hint;
49
50
  if (hasPresented) {
50
51
  hasPresented = false;
51
52
  tooltipPresenter.hide(presentationToken);
@@ -119,7 +120,6 @@ export function attachHoverLabels(
119
120
  () => viewer.domElement.removeEventListener("pointerup", onUp),
120
121
  () => viewer.domElement.removeEventListener("pointerleave", onLeave),
121
122
  hide,
122
- () => highlight.dispose(),
123
123
  () => { if (ownsTooltip) tooltipPresenter.dispose(); },
124
124
  ], "feature hover cleanup failed");
125
125
  },
@@ -14,7 +14,10 @@ export function createTooltipPresenter({ id = "pf-hover-tip" } = {}) {
14
14
  const title = document.createElement("b");
15
15
  const subtitle = document.createElement("span");
16
16
  subtitle.className = "pf-hover-sub";
17
- element.append(title, subtitle);
17
+ // A call-to-action line under the name ("Click to edit"); empty = hidden.
18
+ const hint = document.createElement("div");
19
+ hint.className = "pf-hover-hint";
20
+ element.append(title, subtitle, hint);
18
21
  document.body.appendChild(element);
19
22
  let disposed = false;
20
23
  const claims = [];
@@ -22,6 +25,7 @@ export function createTooltipPresenter({ id = "pf-hover-tip" } = {}) {
22
25
  function setContent(content) {
23
26
  title.textContent = content.title;
24
27
  subtitle.textContent = content.subtitle ?? "";
28
+ hint.textContent = content.hint ?? "";
25
29
  }
26
30
 
27
31
  function measureVisible(content, anchored) {