@booboo-brain/panel 0.5.4 → 0.5.6

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.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import * as react from 'react';
2
2
 
3
3
  type ApiFn = (path: string, init?: RequestInit) => Promise<any>;
4
- declare function Panel({ api }?: {
4
+ declare function Panel({ api, theme }?: {
5
5
  api?: ApiFn;
6
+ theme?: "dark" | "light";
6
7
  }): react.JSX.Element;
7
8
 
8
9
  declare const PANEL_VERSION: string;
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { orgBootSlice, relTime } from "@booboo-brain/spec";
5
5
  // src/panel-css.ts
6
6
  var PANEL_CSS = String.raw`
7
7
  /* GENERATED from design/tokens.json by scripts/gen-tokens.mjs — DO NOT EDIT */
8
- :root {
8
+ .pnl, .pnl-fatal {
9
9
  --bg: #06080e;
10
10
  --bg-lift: #0a0d15;
11
11
  --card: #0f131c;
@@ -59,7 +59,12 @@ var PANEL_CSS = String.raw`
59
59
  the dachshund's collar), a slow aurora breathing behind everything, cards
60
60
  that lift and settle, numbers that count up. Dark by default. */
61
61
 
62
- :root {
62
+ /* Scoped to the panel's own roots, NEVER :root — see scripts/gen-tokens.mjs.
63
+ A custom property on :root is shadowed by any ancestor declaring the same
64
+ name, so a host whose shell declares --bg-1/--line/--gold silently repaints
65
+ the board. Declaring them here means nothing can sit between these names and
66
+ the elements that read them. */
67
+ .pnl, .pnl-fatal {
63
68
  /* THE STAFF BOARD — a master-key rack, not a dashboard.
64
69
  Structure comes from two real artefacts: a master-key hierarchy (passe
65
70
  général → passe partiel → cylinder, which is literally how a hotel is
@@ -118,8 +123,10 @@ var PANEL_CSS = String.raw`
118
123
  }
119
124
 
120
125
  /* DARK — the wine-and-brass board, now one toggle away instead of the default.
121
- Same structure, same shadows-as-depth logic, inverted ground. */
122
- :root[data-theme="dark"] {
126
+ Same structure, same shadows-as-depth logic, inverted ground.
127
+ Scoped to the panel's own root (not :root) for two reasons: the panel must
128
+ not read a HOST's data-theme as its own, and it must not write one either. */
129
+ .pnl[data-theme="dark"], .pnl-fatal[data-theme="dark"] {
123
130
  --bg: var(--wine);
124
131
  --bg-1: var(--plate);
125
132
  --bg-2: var(--wine2);
@@ -146,15 +153,19 @@ var PANEL_CSS = String.raw`
146
153
  --sh-2: 0 2px 4px rgba(0, 0, 0, .5), 0 16px 34px -18px rgba(0, 0, 0, .85);
147
154
  --sh-3: 0 4px 8px rgba(0, 0, 0, .5), 0 22px 48px -18px rgba(0, 0, 0, .9);
148
155
  }
149
- [data-theme="dark"] body { background: var(--wine); }
150
- [data-theme="dark"] .ag { background: linear-gradient(168deg, var(--plate-lift), #180e16); }
151
- [data-theme="dark"] .bar, [data-theme="dark"] .tabs { background: rgba(11, 5, 9, 0.72); }
152
- [data-theme="dark"] .rack .wt { background: #fff; border-color: transparent; }
153
- [data-theme="dark"] .pnl-stars { opacity: .45; }
156
+ /* The [data-theme="dark"] body rule that used to live here is GONE. A mounted
157
+ component has no business repainting its host's <body> — in Dionisos OS it
158
+ turned the cockpit's body white while the cards stayed dark, which is half of
159
+ what "split-brain" looked like. The standalone app paints its own body in
160
+ app/index.html, which is the page that actually owns it. */
161
+ .pnl[data-theme="dark"] .ag { background: linear-gradient(168deg, var(--plate-lift), #180e16); }
162
+ .pnl[data-theme="dark"] .bar, .pnl[data-theme="dark"] .tabs { background: rgba(11, 5, 9, 0.72); }
163
+ .pnl[data-theme="dark"] .rack .wt { background: #fff; border-color: transparent; }
164
+ .pnl[data-theme="dark"] .pnl-stars { opacity: .45; }
154
165
 
155
- * { box-sizing: border-box; }
156
- body { font-family: var(--font); color: var(--ink); background: var(--bg); overflow-x: hidden; }
166
+ .pnl, .pnl-fatal, .pnl *, .pnl-fatal * { box-sizing: border-box; }
157
167
 
168
+ .pnl, .pnl-fatal { font-family: var(--font); color: var(--ink); background: var(--bg); }
158
169
  .pnl { display: flex; flex-direction: column; position: relative; overflow: hidden; }
159
170
 
160
171
  /* the ground — warm wine light pooling from the top-left, exactly as the hero
@@ -422,7 +433,8 @@ body { font-family: var(--font); color: var(--ink); background: var(--bg); overf
422
433
  .pending-item:hover i { background: var(--warn); color: var(--bg-1); }
423
434
 
424
435
  /* the lane lights up as a drop target, so a near-miss is impossible */
425
- .oc-row.lanes > .oc-child.lane-over {
436
+ .oc-row.lanes > .oc-child.lane-over,
437
+ .oc-row.solo-pack > .oc-child.lane-over {
426
438
  border-color: var(--accent); background: var(--accent-dim);
427
439
  box-shadow: 0 0 0 3px var(--accent-dim), var(--sh-2);
428
440
  }
@@ -585,12 +597,21 @@ body { font-family: var(--font); color: var(--ink); background: var(--bg); overf
585
597
  row — 2,400px natural width, which fit-to-width then scaled to 52% and made
586
598
  every plate unreadable. Rank IV wraps; the board stays legible. */
587
599
  grid-template-columns: max-content var(--rail-w, 56px) minmax(420px, 940px);
588
- grid-template-areas: "gm rail lanes";
600
+ /* Row 2 carries the PACK (childless rank-III units). It is a second row and
601
+ not a second area on row 1 because two items cannot share one named area
602
+ without overlapping — and the gm/rail cells are deliberately left EMPTY on
603
+ row 2 so the GM stays in row 1 rather than stretching down the whole board
604
+ (the C18 defect, in reverse: there the lanes spanned two rows and pinned
605
+ the GM to the top of the first). */
606
+ grid-template-areas:
607
+ "gm rail lanes"
608
+ ". . pack";
589
609
  align-items: start;
590
610
  }
591
611
  .cascade > .ocn > .casc-head { grid-area: gm; display: flex; flex-direction: column; align-items: stretch; }
592
612
  .cascade > .ocn > .oc-down { grid-area: rail; }
593
613
  .cascade > .ocn > .oc-row.lanes { grid-area: lanes; }
614
+ .cascade > .ocn > .oc-row.solo-pack { grid-area: pack; }
594
615
 
595
616
  /* the SVG rail plane — sits under the plates, never intercepts a click */
596
617
  .rails { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: visible; width: 100%; height: 100%; }
@@ -631,6 +652,36 @@ body { font-family: var(--font); color: var(--ink); background: var(--bg); overf
631
652
  transition: border-color .25s ease, box-shadow .35s var(--ease);
632
653
  }
633
654
  .oc-row.lanes > .oc-child:hover { border-color: var(--line-2, #d9d3c9); box-shadow: var(--sh-2); }
655
+
656
+ /* ── THE PACK: rank-III units that have no staff ───────────────────────────
657
+ A lane is a shelf for a department AND ITS PEOPLE. A unit with nobody under
658
+ it has no shelf to fill — it is a card, and 43 cards each claiming a
659
+ 940px-wide tray is what turned the live fleet into a 12,000px queue
660
+ (GAPS C34). They pack instead: same tray material, sized to the card, wrapped
661
+ into rows. Still drop targets, still railed. */
662
+ .oc-row.solo-pack {
663
+ display: grid;
664
+ grid-template-columns: repeat(auto-fill, minmax(278px, 1fr));
665
+ gap: 14px;
666
+ width: 100%;
667
+ margin-top: 14px;
668
+ align-items: stretch;
669
+ }
670
+ .oc-row.solo-pack > .oc-child {
671
+ position: relative;
672
+ border: 1px solid var(--line);
673
+ border-radius: 18px;
674
+ background: var(--surface-tray);
675
+ padding: 14px;
676
+ transition: border-color .25s ease, box-shadow .35s var(--ease);
677
+ }
678
+ .oc-row.solo-pack > .oc-child:hover { border-color: var(--line-2, #d9d3c9); box-shadow: var(--sh-2); }
679
+ /* the pack owns its connectors, exactly as the lanes do */
680
+ .oc-row.solo-pack > .oc-child::before,
681
+ .oc-row.solo-pack > .oc-child::after { display: none; }
682
+ .oc-row.solo-pack > .oc-child > .ocn > .oc-down { display: none; }
683
+ .oc-row.solo-pack > .oc-child > .ocn { display: block; }
684
+ .oc-row.solo-pack > .oc-child > .ocn > .ag { width: 100%; }
634
685
  @media (prefers-reduced-motion: reduce) {
635
686
  .oc-row.lanes > .oc-child:hover { transform: none; }
636
687
  }
@@ -1161,17 +1212,18 @@ function readTheme() {
1161
1212
  return "light";
1162
1213
  }
1163
1214
  }
1164
- function useTheme() {
1215
+ function useTheme(pinned) {
1165
1216
  const [theme, setTheme] = useState(readTheme);
1166
1217
  const chosen = useRef(false);
1167
1218
  useEffect(() => {
1168
- document.documentElement.setAttribute("data-theme", theme);
1219
+ if (pinned) return;
1169
1220
  if (!chosen.current) return;
1170
1221
  try {
1171
1222
  localStorage.setItem(THEME_KEY, theme);
1172
1223
  } catch {
1173
1224
  }
1174
- }, [theme]);
1225
+ }, [theme, pinned]);
1226
+ if (pinned) return [pinned, null];
1175
1227
  return [theme, () => {
1176
1228
  chosen.current = true;
1177
1229
  setTheme((t) => t === "dark" ? "light" : "dark");
@@ -1527,6 +1579,9 @@ function ChartNode({
1527
1579
  }) {
1528
1580
  const kidsRaw = org.agents.filter((c) => c.parent === a.id && c.kind !== "automation");
1529
1581
  const kids = depth === 0 ? [...kidsRaw].sort((x, y) => x.id.localeCompare(y.id)) : kidsRaw;
1582
+ const hasStaff = (k) => org.agents.some((x) => x.parent === k.id && x.kind !== "automation");
1583
+ const laneKids = depth === 0 ? kids.filter(hasStaff) : kids;
1584
+ const soloKids = depth === 0 ? kids.filter((k) => !hasStaff(k)) : [];
1530
1585
  const machines = (() => {
1531
1586
  const out = [];
1532
1587
  const walk = (pid) => {
@@ -1652,17 +1707,30 @@ function ChartNode({
1652
1707
  " staff \xB7 folded to department level \u2014 click to expand"
1653
1708
  ]
1654
1709
  }
1655
- ) : /* @__PURE__ */ jsx("div", { className: `oc-row${depth > 0 && kids.length > 3 ? " wrap" : ""}${depth === 0 ? " lanes" : ""}`, children: kids.map((k, i) => /* @__PURE__ */ jsx(
1656
- Lane,
1657
- {
1658
- laneId: k.id,
1659
- isLane: depth === 0,
1660
- dragId: cardProps.dragId,
1661
- onDropOn: cardProps.onDropOn,
1662
- children: /* @__PURE__ */ jsx(ChartNode, { org, a: k, depth: depth + 1, order: i, ...cardProps })
1663
- },
1664
- k.id
1665
- )) })
1710
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1711
+ /* @__PURE__ */ jsx("div", { className: `oc-row${depth > 0 && kids.length > 3 ? " wrap" : ""}${depth === 0 ? " lanes" : ""}`, children: laneKids.map((k, i) => /* @__PURE__ */ jsx(
1712
+ Lane,
1713
+ {
1714
+ laneId: k.id,
1715
+ isLane: depth === 0,
1716
+ dragId: cardProps.dragId,
1717
+ onDropOn: cardProps.onDropOn,
1718
+ children: /* @__PURE__ */ jsx(ChartNode, { org, a: k, depth: depth + 1, order: i, ...cardProps })
1719
+ },
1720
+ k.id
1721
+ )) }),
1722
+ soloKids.length > 0 && /* @__PURE__ */ jsx("div", { className: "oc-row solo-pack", children: soloKids.map((k, i) => /* @__PURE__ */ jsx(
1723
+ Lane,
1724
+ {
1725
+ laneId: k.id,
1726
+ isLane: true,
1727
+ dragId: cardProps.dragId,
1728
+ onDropOn: cardProps.onDropOn,
1729
+ children: /* @__PURE__ */ jsx(ChartNode, { org, a: k, depth: depth + 1, order: laneKids.length + i, ...cardProps })
1730
+ },
1731
+ k.id
1732
+ )) })
1733
+ ] })
1666
1734
  ] })
1667
1735
  ] });
1668
1736
  }
@@ -2422,7 +2490,7 @@ function GraphScreen({ hasSnapshot }) {
2422
2490
  ] });
2423
2491
  return /* @__PURE__ */ jsx("div", { className: "graph-wrap", children: /* @__PURE__ */ jsx("iframe", { className: "graph-frame", src: "/view/?file=/snapshot.json", title: "Booboo 3D brain" }) });
2424
2492
  }
2425
- function App() {
2493
+ function App({ theme: pinnedTheme }) {
2426
2494
  const api = useApi();
2427
2495
  const [tab, param] = useRoute();
2428
2496
  const [saved, setSaved] = useState(null);
@@ -2553,11 +2621,11 @@ function App() {
2553
2621
  const nodeCount = useCountUp(stats?.nodes ?? 0, 1200);
2554
2622
  const memTotal = useCountUp(totals?.mem ?? 0, 1100);
2555
2623
  const repTotal = useCountUp(totals?.rep ?? 0, 1300);
2556
- const [theme, toggleTheme] = useTheme();
2624
+ const [theme, toggleTheme] = useTheme(pinnedTheme);
2557
2625
  const [showLaw, setShowLaw] = useState(false);
2558
- if (err && !draft) return /* @__PURE__ */ jsx("div", { className: "pnl-fatal", children: err });
2559
- if (!draft) return /* @__PURE__ */ jsx("div", { className: "pnl-fatal calm", children: "waking the organigram\u2026" });
2560
- return /* @__PURE__ */ jsxs("div", { className: "pnl", children: [
2626
+ if (err && !draft) return /* @__PURE__ */ jsx("div", { className: "pnl-fatal", "data-theme": theme, children: err });
2627
+ if (!draft) return /* @__PURE__ */ jsx("div", { className: "pnl-fatal calm", "data-theme": theme, children: "waking the organigram\u2026" });
2628
+ return /* @__PURE__ */ jsxs("div", { className: "pnl", "data-theme": theme, children: [
2561
2629
  /* @__PURE__ */ jsx("div", { className: "pnl-aurora", "aria-hidden": true }),
2562
2630
  /* @__PURE__ */ jsx(Constellation, {}),
2563
2631
  /* @__PURE__ */ jsxs("header", { className: "bar", children: [
@@ -2594,7 +2662,7 @@ function App() {
2594
2662
  children: "\u2696 show the law"
2595
2663
  }
2596
2664
  ),
2597
- /* @__PURE__ */ jsx("button", { className: "btn ghost theme-toggle", title: "light / dark", "aria-label": "toggle light or dark theme", onClick: toggleTheme, children: theme === "dark" ? "\u2600" : "\u263E" }),
2665
+ toggleTheme && /* @__PURE__ */ jsx("button", { className: "btn ghost theme-toggle", title: "light / dark", "aria-label": "toggle light or dark theme", onClick: toggleTheme, children: theme === "dark" ? "\u2600" : "\u263E" }),
2598
2666
  changes.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
2599
2667
  /* @__PURE__ */ jsxs("span", { className: "bar-draft", children: [
2600
2668
  changes.length,
@@ -2653,17 +2721,17 @@ function App() {
2653
2721
  ] }, tab + (param ?? ""))
2654
2722
  ] });
2655
2723
  }
2656
- function Panel({ api = defaultApi } = {}) {
2724
+ function Panel({ api = defaultApi, theme } = {}) {
2657
2725
  return /* @__PURE__ */ jsxs(ApiCtx.Provider, { value: api, children: [
2658
2726
  /* @__PURE__ */ jsx("style", { children: PANEL_CSS }),
2659
- /* @__PURE__ */ jsx(App, {})
2727
+ /* @__PURE__ */ jsx(App, { theme })
2660
2728
  ] });
2661
2729
  }
2662
2730
 
2663
2731
  // package.json
2664
2732
  var package_default = {
2665
2733
  name: "@booboo-brain/panel",
2666
- version: "0.5.4",
2734
+ version: "0.5.6",
2667
2735
  description: "Booboo panel \u2014 the organigram. Run your agents like a company: a drag-drop hierarchy your agents boot from, with buckets, reports, rules and the 3D graph, over one org file + snapshot.",
2668
2736
  license: "MIT",
2669
2737
  repository: {