@principal-ai/subsystems-react 0.28.1 → 0.28.3

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.
@@ -29,8 +29,10 @@ import {
29
29
  packageColor,
30
30
  subsystemGraphLayoutKey,
31
31
  describeConstructBreakdown,
32
+ reorderWalkthroughs,
33
+ reorderTargetIndex,
32
34
  } from './model';
33
- import type { SubsystemComponent, SubsystemComponentEdge } from './model';
35
+ import type { SubsystemComponent, SubsystemComponentEdge, SubsystemWalkthrough } from './model';
34
36
 
35
37
  const comps: SubsystemComponent[] = [
36
38
  { alias: 'reader', name: 'SessionReader', construct: 'class', file: 'SessionReader.ts', purl: 'pkg:github/principal-ai/agent-monitoring' },
@@ -793,3 +795,76 @@ describe('module badge labels', () => {
793
795
  expect(moduleBadgeWidth('src/…/nodes.tsx')).toBeLessThan(moduleBadgeWidth('packages/…/nodes.tsx'));
794
796
  });
795
797
  });
798
+
799
+ describe('reorderWalkthroughs', () => {
800
+ const wts: SubsystemWalkthrough[] = [
801
+ { id: 'a', title: 'A', steps: [] },
802
+ { id: 'b', title: 'B', steps: [] },
803
+ { id: 'c', title: 'C', steps: [] },
804
+ ];
805
+ const ids = (list: SubsystemWalkthrough[]) => list.map((w) => w.id);
806
+
807
+ test('moves an item forward to the target index', () => {
808
+ expect(ids(reorderWalkthroughs(wts, 0, 2))).toEqual(['b', 'c', 'a']);
809
+ });
810
+
811
+ test('moves an item backward to the target index', () => {
812
+ expect(ids(reorderWalkthroughs(wts, 2, 0))).toEqual(['c', 'a', 'b']);
813
+ });
814
+
815
+ test('returns a new array and leaves the input untouched', () => {
816
+ const next = reorderWalkthroughs(wts, 1, 2);
817
+ expect(next).not.toBe(wts);
818
+ expect(ids(wts)).toEqual(['a', 'b', 'c']);
819
+ expect(ids(next)).toEqual(['a', 'c', 'b']);
820
+ });
821
+
822
+ test('no-op when source and target match', () => {
823
+ expect(ids(reorderWalkthroughs(wts, 1, 1))).toEqual(['a', 'b', 'c']);
824
+ });
825
+
826
+ test('out-of-range source returns a shallow copy unchanged', () => {
827
+ const next = reorderWalkthroughs(wts, 5, 0);
828
+ expect(next).not.toBe(wts);
829
+ expect(ids(next)).toEqual(['a', 'b', 'c']);
830
+ });
831
+
832
+ test('clamps a target beyond the end to the last slot', () => {
833
+ expect(ids(reorderWalkthroughs(wts, 0, 99))).toEqual(['b', 'c', 'a']);
834
+ });
835
+ });
836
+
837
+ describe('reorderTargetIndex', () => {
838
+ // Boundaries are the n+1 gaps between rows: 0 is above row 0, n is below
839
+ // the last row. Dragging row `from` to boundary `b` lands it at b, minus one
840
+ // when b is past from (removing the row shifts later gaps down).
841
+ test('dragging down past a row lands after it', () => {
842
+ expect(reorderTargetIndex(2, 0)).toBe(1);
843
+ expect(reorderTargetIndex(3, 0)).toBe(2);
844
+ });
845
+
846
+ test('dragging up lands at the boundary', () => {
847
+ expect(reorderTargetIndex(0, 2)).toBe(0);
848
+ expect(reorderTargetIndex(1, 2)).toBe(1);
849
+ });
850
+
851
+ test('the boundaries around the dragged row are no-ops', () => {
852
+ expect(reorderTargetIndex(1, 1)).toBe(1);
853
+ expect(reorderTargetIndex(2, 1)).toBe(1);
854
+ });
855
+
856
+ test('dropping the first row above itself is a no-op', () => {
857
+ expect(reorderTargetIndex(0, 0)).toBe(0);
858
+ });
859
+
860
+ test('composes with reorderWalkthroughs to match the indicator', () => {
861
+ const wts: SubsystemWalkthrough[] = [
862
+ { id: 'a', title: 'A', steps: [] },
863
+ { id: 'b', title: 'B', steps: [] },
864
+ { id: 'c', title: 'C', steps: [] },
865
+ ];
866
+ // Drag A onto the gap below B (boundary 2) → A lands between B and C.
867
+ const target = reorderTargetIndex(2, 0);
868
+ expect(reorderWalkthroughs(wts, 0, target).map((w) => w.id)).toEqual(['b', 'a', 'c']);
869
+ });
870
+ });
@@ -349,6 +349,37 @@ export function walkthroughStepGraphEdgeId(
349
349
  return derivedGraphEdgeId(step.from, step.to, step.mechanism);
350
350
  }
351
351
 
352
+ /**
353
+ * Move one walkthrough from `from` to `to`, returning a new array. Used by the
354
+ * flows panel's drag-to-reorder: the array order is the walkthroughs' display
355
+ * order, so a reorder is just an array splice. Out-of-range `from` returns a
356
+ * shallow copy unchanged; `to` is clamped into range.
357
+ */
358
+ export function reorderWalkthroughs(
359
+ walkthroughs: readonly SubsystemWalkthrough[],
360
+ from: number,
361
+ to: number,
362
+ ): SubsystemWalkthrough[] {
363
+ const next = [...walkthroughs];
364
+ if (!Number.isInteger(from) || from < 0 || from >= next.length) return next;
365
+ const target = Math.max(0, Math.min(to, next.length - 1));
366
+ if (target === from) return next;
367
+ const [moved] = next.splice(from, 1);
368
+ if (moved) next.splice(target, 0, moved);
369
+ return next;
370
+ }
371
+
372
+ /**
373
+ * Final index for a drag-reorder given the insertion `boundary` (one of the
374
+ * `n + 1` gaps between rows) and the dragged row's `from` index. Removing the
375
+ * dragged row shifts every boundary after it down one, so a boundary past
376
+ * `from` maps to `boundary - 1`. Feed the result to `reorderWalkthroughs` as
377
+ * `to`; a result equal to `from` is a no-op.
378
+ */
379
+ export function reorderTargetIndex(boundary: number, from: number): number {
380
+ return boundary - (boundary > from ? 1 : 0);
381
+ }
382
+
352
383
  export function deriveGraphEdges(doc: {
353
384
  relations?: readonly SubsystemRelation[];
354
385
  walkthroughs?: readonly SubsystemWalkthrough[];