@arcote.tech/arc-process 0.7.28

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.
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Zwijanie kontekstów na diagramie procesu.
3
+ *
4
+ * Kroki, których `contextPath` zawiera ZWINIĘTY kontekst, zlewają się —
5
+ * przyległymi seriami w kolejności dokumentu — w jeden pseudo-krok rodzaju
6
+ * "context" (box ukrywający wewnętrzne eventy/widoki). Relacje do środka
7
+ * layout re-targetuje na box. `<Arc context="…">` produkuje taki pseudo-krok
8
+ * wprost (już w modelu), więc zlewa się z przyległymi krokami tego kontekstu.
9
+ *
10
+ * Reguła domyślna (tryb `defaultCollapsed=true`, „górny rzut”): zwinięte są
11
+ * wszystkie konteksty POZA kontekstem domowym dokumentu (`homeContextId`)
12
+ * i jego przodkami — narracja własnego pakietu pozostaje widoczna, a obce
13
+ * fragmenty (np. czat AI) zamykają się w boxach. Stan czytelnika
14
+ * (`collapsedContexts`) nadpisuje domyślne per kontekst.
15
+ */
16
+
17
+ import type { ProcessViewState } from "../state/view-state";
18
+ import {
19
+ CONTEXT_REF_KIND,
20
+ type ContextInfo,
21
+ type ProcessModel,
22
+ type ProcessStep,
23
+ } from "./types";
24
+
25
+ export interface CollapseOptions {
26
+ contexts: ContextInfo[];
27
+ state: ProcessViewState;
28
+ /** Tryb domyślny: true = górny rzut (obce konteksty zwinięte). */
29
+ defaultCollapsed: boolean;
30
+ /** Kontekst domowy dokumentu — jego narracja nie zwija się domyślnie. */
31
+ homeContextId?: string;
32
+ }
33
+
34
+ export interface CollapsedGroup {
35
+ /** occId pseudo-kroku grupy. */
36
+ occId: string;
37
+ /** Zwinięty kontekst. */
38
+ context: ContextInfo;
39
+ /** occId kroków-członków (w kolejności dokumentu). */
40
+ memberOccIds: string[];
41
+ /** Id nodów członków — do re-targetowania relacji na box. */
42
+ memberNodeIds: Set<string>;
43
+ }
44
+
45
+ export interface CollapseResult {
46
+ /** Kroki po zwinięciu: pseudo-kroki grup zastępują serie członków. */
47
+ steps: ProcessStep[];
48
+ groups: CollapsedGroup[];
49
+ /** occId członka → occId grupy. */
50
+ groupByOcc: Map<string, string>;
51
+ /** id noda członka → occId grupy (re-target krawędzi). */
52
+ groupByNodeId: Map<string, string>;
53
+ }
54
+
55
+ /** Czy kontekst jest zwinięty w danym stanie/trybie. */
56
+ export function isContextCollapsed(
57
+ ctx: ContextInfo,
58
+ opts: CollapseOptions,
59
+ ): boolean {
60
+ const explicit = opts.state.collapsedContexts[ctx.id];
61
+ if (explicit !== undefined) return explicit;
62
+ if (!opts.defaultCollapsed) return false;
63
+ // Kontekst bez zadeklarowanej nazwy (id z hasha) nie jest sensownym boxem —
64
+ // anonimowe konteksty pozostają przezroczyste.
65
+ if (!ctx.name && !ctx.title) return false;
66
+ // Kontekst domowy i jego przodkowie pozostają otwarci.
67
+ if (opts.homeContextId) {
68
+ const home = opts.contexts.find((c) => c.id === opts.homeContextId);
69
+ if (home) {
70
+ const isAncestorOrHome =
71
+ ctx.path.length <= home.path.length &&
72
+ ctx.path.every((seg, i) => home.path[i] === seg);
73
+ if (isAncestorOrHome) return false;
74
+ }
75
+ }
76
+ return true;
77
+ }
78
+
79
+ /**
80
+ * Najbardziej ZEWNĘTRZNY zwinięty kontekst na ścieżce kroku — gdy zwinięty
81
+ * jest kontekst nadrzędny, wszystko w środku zlewa się do jego boxa.
82
+ */
83
+ function collapsedPrefixOf(
84
+ step: ProcessStep,
85
+ byId: Map<string, ContextInfo>,
86
+ opts: CollapseOptions,
87
+ ): ContextInfo | undefined {
88
+ const path = step.node?.contextPath;
89
+ if (!path) return undefined;
90
+ for (const id of path) {
91
+ const ctx = byId.get(id);
92
+ if (ctx && isContextCollapsed(ctx, opts)) return ctx;
93
+ }
94
+ return undefined;
95
+ }
96
+
97
+ /** Pseudo-krok grupy zwiniętego kontekstu. */
98
+ function groupStep(
99
+ ctx: ContextInfo,
100
+ members: ProcessStep[],
101
+ occId: string,
102
+ ): ProcessStep {
103
+ const first = members[0];
104
+ return {
105
+ occId,
106
+ seq: first.seq,
107
+ ref: { nodeId: ctx.id, kind: CONTEXT_REF_KIND, key: `context:${ctx.id}` },
108
+ exists: true,
109
+ node: {
110
+ id: `ctx:${ctx.id}`,
111
+ kind: CONTEXT_REF_KIND,
112
+ label: ctx.title ?? ctx.name ?? ctx.id,
113
+ contextPath: ctx.path,
114
+ meta: { context: ctx, memberCount: members.length },
115
+ },
116
+ label: ctx.title ?? ctx.name ?? ctx.id,
117
+ attachments: {},
118
+ notes: members.flatMap((m) => m.notes),
119
+ depth: Math.min(...members.map((m) => m.depth)),
120
+ lane: first.lane,
121
+ alt: first.alt,
122
+ stepTitle: first.stepTitle,
123
+ laneTitle: first.laneTitle,
124
+ blockId: first.blockId,
125
+ phaseId: first.phaseId,
126
+ entryId: first.entryId,
127
+ entryTitle: first.entryTitle,
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Zlej przyległe (w kolejności dokumentu) kroki z tym samym zwiniętym
133
+ * prefiksem kontekstu w pseudo-kroki grup.
134
+ */
135
+ export function collapseContexts(
136
+ model: ProcessModel,
137
+ opts: CollapseOptions,
138
+ ): CollapseResult {
139
+ const byId = new Map(opts.contexts.map((c) => [c.id, c]));
140
+ const steps: ProcessStep[] = [];
141
+ const groups: CollapsedGroup[] = [];
142
+ const groupByOcc = new Map<string, string>();
143
+ const groupByNodeId = new Map<string, string>();
144
+
145
+ let run: { ctx: ContextInfo; members: ProcessStep[] } | null = null;
146
+ const flush = () => {
147
+ if (!run) return;
148
+ const { ctx, members } = run;
149
+ run = null;
150
+ if (members.length === 1 && members[0].node?.kind === CONTEXT_REF_KIND) {
151
+ // Samotny `<Arc context>` — już jest boxem, nie owijamy drugi raz.
152
+ steps.push(members[0]);
153
+ return;
154
+ }
155
+ const occId = `grp:${ctx.id}:${members[0].seq}`;
156
+ const g: CollapsedGroup = {
157
+ occId,
158
+ context: ctx,
159
+ memberOccIds: members.map((m) => m.occId),
160
+ memberNodeIds: new Set(
161
+ members.filter((m) => m.exists).map((m) => m.node!.id),
162
+ ),
163
+ };
164
+ groups.push(g);
165
+ for (const m of members) groupByOcc.set(m.occId, occId);
166
+ for (const id of g.memberNodeIds) groupByNodeId.set(id, occId);
167
+ steps.push(groupStep(ctx, members, occId));
168
+ };
169
+
170
+ for (const step of model.steps) {
171
+ // `<Arc context>` liczy się jak krok swojego kontekstu (zlewa się z sąsiadami).
172
+ const ctx =
173
+ step.node?.kind === CONTEXT_REF_KIND
174
+ ? byId.get(step.node.contextPath?.[step.node.contextPath.length - 1] ?? "")
175
+ : collapsedPrefixOf(step, byId, opts);
176
+ // Członkiem serii jest krok zwiniętego kontekstu; pseudo-krok `<Arc context>`
177
+ // zlewa się tylko, gdy jego kontekst też jest zwinięty.
178
+ const collapsedCtx =
179
+ ctx && (step.node?.kind === CONTEXT_REF_KIND
180
+ ? isContextCollapsed(ctx, opts)
181
+ : true)
182
+ ? ctx
183
+ : undefined;
184
+
185
+ if (collapsedCtx) {
186
+ if (run && run.ctx.id === collapsedCtx.id && sameTrack(run.members[0], step)) {
187
+ run.members.push(step);
188
+ } else {
189
+ flush();
190
+ run = { ctx: collapsedCtx, members: [step] };
191
+ }
192
+ } else {
193
+ flush();
194
+ steps.push(step);
195
+ }
196
+ }
197
+ flush();
198
+
199
+ return { steps, groups, groupByOcc, groupByNodeId };
200
+ }
201
+
202
+ /** Kroki zlewają się tylko w obrębie tego samego toru (Entry/lane). */
203
+ function sameTrack(a: ProcessStep, b: ProcessStep): boolean {
204
+ return a.entryId === b.entryId && a.lane === b.lane;
205
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Profil procesu — warstwa semantyki nad generycznym grafem.
3
+ *
4
+ * Profil definiuje: rodzaje nodów (kolory/ikony/etykiety), relacje wraz z ich
5
+ * semantyką przepływu i stylem, grupy doczepień (co i po której stronie
6
+ * rozwija się wokół kroku) oraz rozwiązywanie referencji z propsów `<Arc>`.
7
+ * Rdzeń modelu i layout są od profilu niezależne.
8
+ */
9
+
10
+ import type {
11
+ Direction,
12
+ MethodRef,
13
+ ProcessNode,
14
+ ResolvedRef,
15
+ } from "./types";
16
+
17
+ /** Wygląd rodzaju noda. */
18
+ export interface NodeKindSpec {
19
+ color: string;
20
+ /** Etykieta rodzaju (fallback: klucz). */
21
+ label?: string;
22
+ icon?: string;
23
+ }
24
+
25
+ /** Semantyka i styl relacji. */
26
+ export interface RelationSpec {
27
+ color: string;
28
+ /** Etykieta na krawędzi (fallback: klucz). */
29
+ label?: string;
30
+ /**
31
+ * Semantyka przepływu: "out" — source produkuje target (strzałka
32
+ * source→target, np. mutates/emits); "in" — source konsumuje target
33
+ * (strzałka target→source, np. queries/listens/handles).
34
+ */
35
+ semantics: "in" | "out";
36
+ /** Krawędź kreskowana (np. użycia UI). */
37
+ dashed?: boolean;
38
+ }
39
+
40
+ /** Grupa doczepień — powiązane nody rozwijane wokół kroku. */
41
+ export interface AttachmentGroupSpec {
42
+ /** Rodzaje relacji zasilające grupę. */
43
+ relations: string[];
44
+ /**
45
+ * Kierunek zbierania: "out" — nody, na które ten nod wskazuje (from=nod);
46
+ * "in" — nody wskazujące na ten nod (to=nod).
47
+ */
48
+ direction: "in" | "out";
49
+ /** Domyślna strona doczepienia (MDX może nadpisać per wystąpienie). */
50
+ defaultSide: Direction;
51
+ color: string;
52
+ /** Czy krawędź doczepki wchodzi DO kroku (true) czy z niego wychodzi. */
53
+ intoNode: boolean;
54
+ label?: string;
55
+ }
56
+
57
+ export interface ProcessProfile {
58
+ id: string;
59
+ /** Rodzaje nodów tego profilu. */
60
+ kinds: Record<string, NodeKindSpec>;
61
+ /** Relacje tego profilu. */
62
+ relations: Record<string, RelationSpec>;
63
+ /** Grupy doczepień (badge/rozwinięcia) wokół kroków. */
64
+ attachmentGroups: Record<string, AttachmentGroupSpec>;
65
+ /**
66
+ * Mapuje propsy `<Arc>` na referencję do noda (bez walidacji istnienia —
67
+ * tę robi model przeciw grafowi). Zwraca null, gdy propsy nie zawierają
68
+ * rozpoznawanej referencji.
69
+ */
70
+ resolveRef(props: Record<string, unknown>): ResolvedRef | null;
71
+ /**
72
+ * Opcjonalne nadpisanie forward-zależności per nod/metoda (np. per-metodowe
73
+ * zależności agregatu Arc). Zwrócone grupy ZASTĘPUJĄ listy wyliczone z
74
+ * relacji grafu; null — użyj wyliczonych.
75
+ */
76
+ forwardDeps?(
77
+ node: ProcessNode,
78
+ method?: MethodRef,
79
+ ): Partial<Record<string, string[]>> | null;
80
+ }
81
+
82
+ /** Pomocnik identycznościowy — utrzymuje pełne typowanie deklaracji profilu. */
83
+ export function defineProfile(profile: ProcessProfile): ProcessProfile {
84
+ return profile;
85
+ }
86
+
87
+ /** Kolor rodzaju noda z profilu (fallback: szary). */
88
+ export function kindColor(profile: ProcessProfile, kind: string): string {
89
+ return profile.kinds[kind]?.color ?? "#868e96";
90
+ }
91
+
92
+ /** Kolor relacji z profilu (fallback: szary). */
93
+ export function relationColor(profile: ProcessProfile, kind: string): string {
94
+ return profile.relations[kind]?.color ?? "#868e96";
95
+ }
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Generyczny model grafu procesów.
3
+ *
4
+ * Rdzeń fragmentu process NIE zna rodzajów elementów Arka — operuje na
5
+ * abstrakcyjnych nodach/relacjach, a semantykę (kolory, ikony, kierunki
6
+ * domyślne, rozwiązywanie referencji) wnosi PROFIL ({@link ProcessProfile}).
7
+ * Dzięki temu to samo API opisuje use case'y Arka i czysto biznesowe
8
+ * procesy (np. BMF), każde z własnym profilem.
9
+ *
10
+ * Plik bez zależności (React-free) — testowalny bunem.
11
+ */
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Kierunki — róża kompasu
15
+ // ---------------------------------------------------------------------------
16
+
17
+ /**
18
+ * 8 kierunków doczepiania elementów wokół noda. Skosy (NE/SE/SW/NW) wyrażają
19
+ * kontynuacje procesów, czysta N/S — doczepki typu notatka. Każda doczepiona
20
+ * karteczka może rekurencyjnie otwierać własne relacje.
21
+ */
22
+ export type Direction = "N" | "NE" | "E" | "SE" | "S" | "SW" | "W" | "NW";
23
+
24
+ /** Składowa pozioma kierunku: -1 (zachód), 0, 1 (wschód). */
25
+ export function directionDx(d: Direction): -1 | 0 | 1 {
26
+ if (d === "W" || d === "NW" || d === "SW") return -1;
27
+ if (d === "E" || d === "NE" || d === "SE") return 1;
28
+ return 0;
29
+ }
30
+
31
+ /** Składowa pionowa kierunku: -1 (północ), 0, 1 (południe). */
32
+ export function directionDy(d: Direction): -1 | 0 | 1 {
33
+ if (d === "N" || d === "NE" || d === "NW") return -1;
34
+ if (d === "S" || d === "SE" || d === "SW") return 1;
35
+ return 0;
36
+ }
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Graf wejściowy
40
+ // ---------------------------------------------------------------------------
41
+
42
+ /** Pojedynczy nod grafu — znaczenie `kind` definiuje profil. */
43
+ export interface ProcessNode {
44
+ id: string;
45
+ kind: string;
46
+ label: string;
47
+ /** Ścieżka kontekstów-przodków (id od korzenia), z core `__contextPath`. */
48
+ contextPath?: string[];
49
+ /** Dane profilu (np. metody agregatu, ścieżka HTTP, schema). */
50
+ meta?: Record<string, unknown>;
51
+ }
52
+
53
+ /** Relacja między nodami — znaczenie `kind` definiuje profil. */
54
+ export interface ProcessRelation {
55
+ from: string;
56
+ to: string;
57
+ kind: string;
58
+ }
59
+
60
+ /** Kontekst (przestrzeń) — węzeł drzewa hierarchii kontekstów. */
61
+ export interface ContextInfo {
62
+ /** Stabilne id kontekstu (core `ArcContext.id`, np. `ctx_chat`). */
63
+ id: string;
64
+ /** Ścieżka id od korzenia do tego kontekstu (włącznie). */
65
+ path: string[];
66
+ name?: string;
67
+ title?: string;
68
+ description?: string;
69
+ /** Czy kontekst deklaruje komponent-wizytówkę (samego komponentu JSON nie przenosi). */
70
+ hasCard?: boolean;
71
+ }
72
+
73
+ /** Kompletny graf wejściowy diagramu. */
74
+ export interface ProcessGraph {
75
+ nodes: ProcessNode[];
76
+ relations: ProcessRelation[];
77
+ contexts?: ContextInfo[];
78
+ }
79
+
80
+ /** Pusty graf — wszystkie referencje będą czerwonymi chipami TODO. */
81
+ export const EMPTY_GRAPH: ProcessGraph = { nodes: [], relations: [], contexts: [] };
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Referencje
85
+ // ---------------------------------------------------------------------------
86
+
87
+ /** Metoda noda (np. metoda agregatu Arc). */
88
+ export interface MethodRef {
89
+ name: string;
90
+ kind: "mutation" | "query";
91
+ }
92
+
93
+ /** Rozwiązana referencja `<Arc>` — mapowanie propsów na nod grafu. */
94
+ export interface ResolvedRef {
95
+ /** Id docelowego noda w {@link ProcessGraph}. */
96
+ nodeId: string;
97
+ /** Rodzaj (z profilu) lub "context" dla referencji całego kontekstu. */
98
+ kind: string;
99
+ method?: MethodRef;
100
+ /** Kanoniczny klucz do dedup / linkowania chip<->nod. */
101
+ key: string;
102
+ }
103
+
104
+ /** Rodzaj referencji kontekstowej (`<Arc context="…">`). */
105
+ export const CONTEXT_REF_KIND = "context";
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Struktura MDX (ścieżki strukturalne)
109
+ // ---------------------------------------------------------------------------
110
+
111
+ /** Segment ścieżki strukturalnej od korzenia MDX do wystąpienia. */
112
+ export interface PathSeg {
113
+ type: "parallel" | "branch" | "lane" | "step" | "entry";
114
+ id: string;
115
+ /** Indeks toru dziecka w parallel/branch. */
116
+ laneIndex?: number;
117
+ title?: string;
118
+ /** Lane'y branchy są alternatywami (XOR). */
119
+ alt?: boolean;
120
+ /** Identyfikator fazy (`<Step id="…">`) — cel dla `<Continue to>`. */
121
+ phaseId?: string;
122
+ }
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // Wystąpienia (rejestrowane przez MDX w kolejności dokumentu)
126
+ // ---------------------------------------------------------------------------
127
+
128
+ /** Wystąpienie kroku — jedno na każdy `<Arc>` w narracji. */
129
+ export interface StepOccurrence {
130
+ type: "step";
131
+ occId: string;
132
+ ref: ResolvedRef | null;
133
+ label: string;
134
+ path?: PathSeg[];
135
+ /** Override strony doczepienia per grupa relacji (`<Arc sides={{mutates:"SE"}}>`). */
136
+ sides?: Partial<Record<string, Direction>>;
137
+ /** Grupy relacji otwarte na starcie (`<Arc expand={["mutates"]}>`). */
138
+ expand?: string[];
139
+ }
140
+
141
+ /** Marker `<Continue to="faza"/>` — zamyka bieżące Entry, wskazuje cel. */
142
+ export interface ContinueOccurrence {
143
+ type: "continue";
144
+ to: string;
145
+ path?: PathSeg[];
146
+ }
147
+
148
+ /** Notatka `<Note side="N">…</Note>` — doczepiana do poprzedzającego kroku. */
149
+ export interface NoteOccurrence {
150
+ type: "note";
151
+ occId: string;
152
+ text: string;
153
+ side?: Direction;
154
+ title?: string;
155
+ path?: PathSeg[];
156
+ }
157
+
158
+ export type Occurrence = StepOccurrence | ContinueOccurrence | NoteOccurrence;
159
+
160
+ // ---------------------------------------------------------------------------
161
+ // Model wynikowy
162
+ // ---------------------------------------------------------------------------
163
+
164
+ /** Notatka doczepiona do kroku. */
165
+ export interface StepNote {
166
+ occId: string;
167
+ side: Direction;
168
+ title?: string;
169
+ text: string;
170
+ }
171
+
172
+ /** Krok procesu — jeden na wystąpienie (bez dedup). */
173
+ export interface ProcessStep {
174
+ occId: string;
175
+ seq: number;
176
+ ref: ResolvedRef | null;
177
+ exists: boolean;
178
+ node?: ProcessNode;
179
+ label: string;
180
+ method?: MethodRef;
181
+ /** Powiązane nody NIEwidoczne na osi, pogrupowane po grupie relacji. */
182
+ attachments: Partial<Record<string, string[]>>;
183
+ /** Override strony per grupa (z MDX). */
184
+ sides?: Partial<Record<string, Direction>>;
185
+ /** Grupy otwarte na starcie (z MDX). */
186
+ expand?: string[];
187
+ notes: StepNote[];
188
+ /** Pozycja pozioma (czas / głębokość zależności), 0-based. */
189
+ depth: number;
190
+ /** Tor pionowy — 0 = oś główna; ujemne = wejścia nad osią. */
191
+ lane: number;
192
+ /** True dla lane'ów branchy (alternatywy / XOR). */
193
+ alt?: boolean;
194
+ stepTitle?: string;
195
+ laneTitle?: string;
196
+ blockId?: string;
197
+ /** Id fazy (`<Step id>`), w której leży krok. */
198
+ phaseId?: string;
199
+ /** Id/tytuł bloku Entry, gdy krok jest punktem wejścia. */
200
+ entryId?: string;
201
+ entryTitle?: string;
202
+ }
203
+
204
+ /** Element-hydraulika na przepływie między dwoma opowiedzianymi krokami. */
205
+ export interface ProcessConnector {
206
+ id: string;
207
+ node: ProcessNode;
208
+ /** seq dwóch opowiedzianych kroków [upstream → downstream]. */
209
+ between: [number, number];
210
+ /** Pozycja 1-based wśród pośredników na ścieżce. */
211
+ order: number;
212
+ /** Liczba pośredników na tej ścieżce. */
213
+ count: number;
214
+ }
215
+
216
+ /** Krawędź „continues” — z ostatniego kroku Entry do pierwszego kroku fazy. */
217
+ export interface ContinueEdge {
218
+ fromOcc: string;
219
+ toOcc: string;
220
+ phaseId: string;
221
+ }
222
+
223
+ export interface ProcessModel {
224
+ steps: ProcessStep[];
225
+ connectors: ProcessConnector[];
226
+ /** Widoczne id nodów: istniejące kroki + connectory. */
227
+ visible: Set<string>;
228
+ /** id noda -> seq pierwszego istniejącego kroku. */
229
+ firstSeqByNode: Map<string, number>;
230
+ continueEdges: ContinueEdge[];
231
+ }