@booboo-brain/panel 0.4.0 → 0.5.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.
package/dist/index.js CHANGED
@@ -246,6 +246,7 @@ body { font-family: var(--font); color: var(--ink); background: var(--bg); overf
246
246
  .oc-mac:hover { border-color: var(--accent); color: var(--ink); transform: translateY(-1px); }
247
247
  .oc-mac.sel { border-color: var(--accent); background: var(--accent-dim); color: var(--ink); }
248
248
  .oc-mac.more { color: var(--ink-3); font-style: italic; }
249
+ .oc-mac.dragging { opacity: 0.45; cursor: grabbing; }
249
250
  .oc-mac.more:hover { color: var(--accent); }
250
251
  .mac-emoji { font-size: 11px; }
251
252
  .mac-name { white-space: nowrap; max-width: 76px; overflow: hidden; text-overflow: ellipsis; }
@@ -290,10 +291,11 @@ body { font-family: var(--font); color: var(--ink); background: var(--bg); overf
290
291
  .pending-item { font-size: 11.5px; color: var(--warn); font-family: var(--mono); }
291
292
  .pnl-err { position: relative; z-index: 2; padding: 10px 26px; color: #e07b6a; font-size: 12.5px; }
292
293
 
293
- /* layout */
294
- .pnl { height: 100vh; }
294
+ /* layout — 100% so a HOST container decides the height (embedded panel);
295
+ the standalone app gives html/body/#root 100%, so served behaviour is unchanged. */
296
+ .pnl { height: 100%; min-height: 480px; }
295
297
  .body { position: relative; z-index: 1; flex: 1; display: flex; min-height: 0; }
296
- .tree { flex: 1; min-width: 0; padding: 24px 26px 24px; overflow: hidden;
298
+ .tree { position: relative; flex: 1; min-width: 0; padding: 24px 26px 24px; overflow: hidden;
297
299
  display: flex; flex-direction: column;
298
300
  /* the ground plane: a faint dot grid (engineered surface, drag affordance)
299
301
  + a verdigris pool anchoring the eye at the root. Static — capture-safe. */
@@ -310,11 +312,23 @@ body { font-family: var(--font); color: var(--ink); background: var(--bg); overf
310
312
  /* the measuring viewport — flex:1 + min-height:0 lets it consume the height
311
313
  .tree gives it; align/justify center the scaled block for narrow orgs. */
312
314
  .chart-fit { position: relative; flex: 1; min-height: 0; display: flex; align-items: flex-start; justify-content: center; overflow: hidden; }
313
- .fit-badge {
314
- position: absolute; right: 10px; bottom: 8px; font-size: 10px; font-family: var(--mono);
315
- color: var(--ink-3); background: var(--bg-2); border: 1px solid var(--line); border-radius: 6px;
316
- padding: 2px 7px; opacity: 0.7; pointer-events: none; animation: settle 0.4s ease both;
315
+ /* user-zoomed: native scroll is the pan; top-left origin keeps the scaled
316
+ overflow fully reachable (top/center origin would clip the left half). */
317
+ .chart-fit.zoomed { overflow: auto; justify-content: flex-start; }
318
+ .chart-fit.zoomed .chart { transform-origin: top left; }
319
+ .zoomer {
320
+ position: absolute; right: 34px; bottom: 32px; z-index: 3; display: flex; align-items: center; gap: 2px;
321
+ font-family: var(--mono); font-size: 11px; color: var(--ink-2);
322
+ background: var(--bg-2); border: 1px solid var(--line); border-radius: 8px; padding: 3px 6px;
323
+ box-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
317
324
  }
325
+ .zoomer button {
326
+ all: unset; cursor: pointer; padding: 2px 8px; border-radius: 5px; color: var(--ink-2);
327
+ font-family: var(--mono); font-size: 12px; line-height: 1.4;
328
+ }
329
+ .zoomer button:hover { background: var(--accent-dim); color: var(--ink); }
330
+ .zoomer button.on { color: var(--accent); }
331
+ .zoomer-pct { min-width: 40px; text-align: center; color: var(--ink-3); }
318
332
  /* min-width:max-content keeps connectors geometrically correct at natural size
319
333
  BEFORE scaling; the measured --fit shrinks the whole block toward its head. */
320
334
  .chart {
@@ -804,8 +818,13 @@ function ChartNode({
804
818
  "button",
805
819
  {
806
820
  type: "button",
807
- className: `oc-mac${cardProps.selected === m.id ? " sel" : ""}`,
808
- title: `${m.name}${m.role ? ` \u2014 ${m.role}` : ""}`,
821
+ className: `oc-mac${cardProps.selected === m.id ? " sel" : ""}${cardProps.dragId === m.id ? " dragging" : ""}`,
822
+ title: `${m.name}${m.role ? ` \u2014 ${m.role}` : ""} \xB7 drag onto an agent to reallocate`,
823
+ draggable: true,
824
+ onDragStart: (e) => {
825
+ e.dataTransfer.effectAllowed = "move";
826
+ cardProps.onDragStart(m.id);
827
+ },
809
828
  onClick: (e) => {
810
829
  e.stopPropagation();
811
830
  cardProps.onSelect(m.id);
@@ -1184,6 +1203,8 @@ function OrgScreen({
1184
1203
  const fitRef = useRef(null);
1185
1204
  const chartRef = useRef(null);
1186
1205
  const [fit, setFit] = useState(1);
1206
+ const [zoom, setZoom] = useState(null);
1207
+ const eff = zoom ?? fit;
1187
1208
  useLayoutEffect(() => {
1188
1209
  const vp = fitRef.current, chart = chartRef.current;
1189
1210
  if (!vp || !chart) return;
@@ -1201,31 +1222,47 @@ function OrgScreen({
1201
1222
  ro.observe(chart);
1202
1223
  return () => ro.disconnect();
1203
1224
  }, [draft, health, selected]);
1225
+ const effRef = useRef(eff);
1226
+ effRef.current = eff;
1227
+ useEffect(() => {
1228
+ const vp = fitRef.current;
1229
+ if (!vp) return;
1230
+ const onWheel = (e) => {
1231
+ if (!e.ctrlKey && !e.metaKey) return;
1232
+ e.preventDefault();
1233
+ const k = e.deltaY < 0 ? 1.12 : 1 / 1.12;
1234
+ setZoom(Math.min(2.5, Math.max(0.2, effRef.current * k)));
1235
+ };
1236
+ vp.addEventListener("wheel", onWheel, { passive: false });
1237
+ return () => vp.removeEventListener("wheel", onWheel);
1238
+ }, []);
1204
1239
  const root = draft.agents.find((a) => a.id === draft.root);
1205
1240
  return /* @__PURE__ */ jsxs("div", { className: "body", onClick: () => setSelected(null), children: [
1206
1241
  /* @__PURE__ */ jsxs("main", { className: "tree", onClick: (e) => e.stopPropagation(), children: [
1207
- /* @__PURE__ */ jsx("p", { className: "tree-hint", children: "drag an agent onto its new parent \xB7 click for its dossier \xB7 machine trays show live health" }),
1208
- /* @__PURE__ */ jsxs("div", { className: "chart-fit", ref: fitRef, children: [
1209
- /* @__PURE__ */ jsx("div", { className: "chart", ref: chartRef, style: { ["--fit"]: fit }, children: root && /* @__PURE__ */ jsx(
1210
- ChartNode,
1211
- {
1212
- org: draft,
1213
- a: root,
1214
- depth: 0,
1215
- order: 0,
1216
- selected,
1217
- dragId,
1218
- health,
1219
- onSelect: setSelected,
1220
- onDragStart: setDragId,
1221
- onDropOn: dropOn
1222
- }
1223
- ) }),
1224
- fit < 0.999 && /* @__PURE__ */ jsxs("span", { className: "fit-badge", children: [
1225
- "fit ",
1226
- Math.round(fit * 100),
1242
+ /* @__PURE__ */ jsx("p", { className: "tree-hint", children: "drag an agent \u2014 or a tray machine \u2014 onto its new parent \xB7 click for its dossier \xB7 machine trays show live health" }),
1243
+ /* @__PURE__ */ jsx("div", { className: `chart-fit${zoom !== null ? " zoomed" : ""}`, ref: fitRef, children: /* @__PURE__ */ jsx("div", { className: "chart", ref: chartRef, style: { ["--fit"]: eff }, children: root && /* @__PURE__ */ jsx(
1244
+ ChartNode,
1245
+ {
1246
+ org: draft,
1247
+ a: root,
1248
+ depth: 0,
1249
+ order: 0,
1250
+ selected,
1251
+ dragId,
1252
+ health,
1253
+ onSelect: setSelected,
1254
+ onDragStart: setDragId,
1255
+ onDropOn: dropOn
1256
+ }
1257
+ ) }) }),
1258
+ /* @__PURE__ */ jsxs("div", { className: "zoomer", children: [
1259
+ /* @__PURE__ */ jsx("button", { type: "button", title: "zoom out (ctrl+wheel)", onClick: () => setZoom(Math.max(0.2, eff / 1.25)), children: "\u2212" }),
1260
+ /* @__PURE__ */ jsxs("span", { className: "zoomer-pct", children: [
1261
+ Math.round(eff * 100),
1227
1262
  "%"
1228
- ] })
1263
+ ] }),
1264
+ /* @__PURE__ */ jsx("button", { type: "button", title: "zoom in (ctrl+wheel)", onClick: () => setZoom(Math.min(2.5, eff * 1.25)), children: "\uFF0B" }),
1265
+ /* @__PURE__ */ jsx("button", { type: "button", className: zoom === null ? "on" : "", title: "fit the whole org in view", onClick: () => setZoom(null), children: "fit" })
1229
1266
  ] })
1230
1267
  ] }),
1231
1268
  selected && /* @__PURE__ */ jsx(