@graphlearning/graphl-flow 0.0.0-stage → 0.2.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
@@ -1,3 +1,165 @@
1
- # Temporary Holding Version
1
+ # @graphlearning/graphl-flow
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Declarative architecture diagrams for React. Describe **groups**, **items** and **edges**; the
4
+ browser lays them out and the engine draws the arrows. Scenes never contain x/y.
5
+
6
+ > 0.1.0 — seven node types (`group`, `item`, `table`, `code`, `plot`, `tree`, `note`), edges,
7
+ > two themes, fit-to-frame for 4K recording. Content repos pin a version and upgrade deliberately.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm i @graphlearning/graphl-flow react react-dom lucide-react
13
+ ```
14
+
15
+ ```tsx
16
+ import '@graphlearning/graphl-flow/styles.css' // once, at the app root
17
+ import { Scene, type SceneModel } from '@graphlearning/graphl-flow'
18
+
19
+ const scene: SceneModel = {
20
+ title: 'Batch & Real-Time',
21
+ dir: 'row',
22
+ nodes: [
23
+ { type: 'group', id: 'src', title: 'Data Sources', tone: 'source', children: [
24
+ { type: 'item', id: 'crm', title: 'CRM' },
25
+ ] },
26
+ { type: 'group', id: 'ing', title: 'Ingestion', badge: 1, tone: 'ingest', children: [
27
+ { type: 'item', id: 'adf', title: 'Azure Data Factory', sub: 'Scheduled pipelines' },
28
+ ] },
29
+ ],
30
+ edges: [{ from: 'src', to: 'ing' }],
31
+ }
32
+
33
+ <div style={{ width: 3840, height: 2160 }}> {/* the Scene fills its parent — give it a size */}
34
+ <Scene scene={scene} theme="dark" />
35
+ </div>
36
+ ```
37
+
38
+ **Edges** connect any two nodes by id — items or groups, at any depth — as right-angle arrows.
39
+ An edge leaves across the larger gap between the two columns (or rows) it connects, so it stays in
40
+ the gaps rather than cutting through a neighbour; ends that face each other connect straight. A face with several edges gives each its
41
+ own port, and their labels move off the line (above/below) so they don't collide. The gap an edge
42
+ runs through widens automatically, to fit its label when it has one. An edge between a node and its
43
+ own ancestor is not drawn. `bidirectional: true` puts a head at both ends.
44
+
45
+ **Pulse.** A dot travels every edge once per **2.4 s**, linear — both ways on a bidirectional edge.
46
+ That period is the recorder's loop (`PULSE_S` in `@graphlearning/shell`'s recorders), so a looped
47
+ video has no jump at the join. `pulse: false` on a scene turns it off. `npm run capture` freezes the
48
+ animation at t = 0, so stills are reproducible.
49
+
50
+ **`spread: true`** on a group spaces its children evenly over its full height when the group is
51
+ stretched taller than them (a short column beside a tall one).
52
+
53
+ **Tables and memory.** A `table` is a DB schema, a stack frame, a heap object, an array or a hash
54
+ map's buckets. Every row is addressable as `<table id>.<first cell>` (or `.<id>` when you set one), so
55
+ a pointer is just an edge: `{ from: 'main.nums', to: 'list' }`. There is no memory node type —
56
+ stack and heap are groups of tables:
57
+
58
+ ```ts
59
+ { type: 'group', id: 'stack', title: 'Stack', children: [
60
+ { type: 'table', id: 'main', title: '<module>', rows: [['nums', '●'], ['total', 3]] },
61
+ ] },
62
+ { type: 'table', id: 'list', title: 'list', columns: ['index', 'ref'], rows: [['0', '●'], ['1', '●']] },
63
+ // edges: [{ from: 'main.nums', to: 'list' }]
64
+ ```
65
+
66
+ A row connects through its left or right end only. Lay a pointer's target BESIDE its row (the next
67
+ column), not below it — an edge is routed in the gap between two siblings, never around a third.
68
+ `tag` draws a short pill before a row (`PK`, `FK`, `sp`); `tone` on a row tints it.
69
+
70
+ **Code.** `code` takes the source as a (template-literal) string; its common indent is dropped.
71
+ `lang` is `python | sql | scala | java | js | ts | shell | text` — one small highlighter with seven
72
+ token colours per theme. `show` keeps only those line ranges and folds each skipped run into
73
+ `··· N lines`; `show` and `highlight` use the FILE's line numbers, and so do edges: `src.L17`.
74
+ `terminal: true` draws a terminal — `$ ` lines are commands, `! ` lines errors, the rest output.
75
+
76
+ **IDE frames.** Three columns: `tree` | (editor `code` over terminal `code`, in a `bare` group) |
77
+ a SIDE PANE — a `bare` group with a fixed width `w`. The IDE alone is narrower than 16:9; the side
78
+ pane fills the frame and holds whatever the moment needs (a `note`, a `compact` plot, a small memory
79
+ diagram) or nothing. See `dev/fixtures/studies/ide-frame.ts`.
80
+
81
+ `minW`/`minH` are FLOORS and `maxW`/`maxH` CAPS, in the scene's own pixels; `w`/`h` set both. Give
82
+ every frame of a video the same floors and a fixed side pane, and they get the same zoom: no pane
83
+ moves between cuts, whatever the side pane holds. Prose wraps to the pane; tables and code do not,
84
+ so keep them narrow. A plot never scales — use `compact: true` (a 420×280 area) in a side pane.
85
+ Don't draw an edge between the IDE's columns: an arrow widens the gap it crosses and moves every
86
+ pane. Point at code with `highlight` and the narration; edges INSIDE the side pane are fine.
87
+
88
+ **Charts.** A `plot`'s x/y values are DATA in the axes' own units — sample curves in the scene
89
+ file. A series is `line | scatter | area | bar | segment`. Every plot's drawing area is the same
90
+ fixed size, so charts line up across a deck. Series take the next colour of a fixed ramp
91
+ (blue, orange, violet, green) unless given a `tone`. A `segment` is a dashed construction or threshold
92
+ line — muted grey unless toned, solid with `dashed: false`, a vector with `arrow: true`. Bars label
93
+ themselves in a legend; other series label their last point.
94
+
95
+ An edge to an id the scene doesn't have logs a console error naming it.
96
+
97
+ **Legibility on video.** The fit scales the whole scene to the frame, so what decides how big text
98
+ looks is how WIDE the scene is for the text it holds — not the font size. Item text wraps at a
99
+ reading measure (15em) instead of widening its column; icons are sized in em, so they keep their
100
+ ratio to the text; a small scene stops growing at 4×. Keep scenes legible: the engine's own check
101
+ fails any fixture whose smallest body text renders under 24px in the 4K frame (12px at 1080p). If a
102
+ diagram hits that, split the slide or trim bullets — a denser scene can only get smaller.
103
+
104
+ **Focus.** `<Scene scene={…} focus="driver" />` singles out one node — any id, a table row or a code
105
+ line included — with a ring and a glow in its own tone. It is drawn with box-shadow, so it moves
106
+ nothing: every section of a course can focus a different node on the same scene without a jump.
107
+
108
+ **Fit.** `Scene` fills its parent and scales the scene to fit, centred, with a 4% margin. The scale
109
+ depends only on the parent's size and the scene's natural size, so the same scene in the same frame
110
+ always lands on the same pixels. The view is locked (no zoom/pan); page scroll passes through and text
111
+ stays selectable. The root carries `data-fit="ready"` once fonts have loaded, edges are routed and the fit is applied —
112
+ wait on it before recording.
113
+
114
+ **Recording at 4K.** Give the Scene a 3840×2160 box and record at device scale 1 — the frame
115
+ `@graphlearning/shell`'s recorders use. In this repo: `npm run capture -- <fixtureId> --theme dark`
116
+ writes `captures/<id>-<theme>.png`.
117
+
118
+ ## Model
119
+
120
+ | Type | Fields |
121
+ |---------|--------|
122
+ | `item` | `id`, `title`, `sub?`, `icon?`, `bullets?`, `stack?`, `chip?`, `tone?`, `span?` |
123
+ | `group` | `id`, `children`, `title?`, `sub?`, `icon?`, `badge?`, `dir?: 'row' \| 'col' \| 'grid'`, `cols?`, `bare?`, `spread?`, `tone?`, `span?` |
124
+ | `table` | `id`, `rows`, `columns?`, `title?`, `tone?`, `span?` — a row is `[cells…]` or `{ cells, id?, tag?, tone? }` |
125
+ | `code` | `id`, `code`, `lang?`, `filename?`, `show?: [from, to][]`, `highlight?: number[]`, `terminal?`, `tone?`, `span?` |
126
+ | `plot` | `id`, `x`, `y` (axes: `min`, `max`, `step?`, `label?`, or `labels` for categories), `series`, `title?`, `axes?`, `grid?` |
127
+ | `tree` | `id`, `entries` (`name`, `children?`, `folder?`, `state?: active \| new \| muted`, `note?`, `icon?`), `title?` |
128
+ | `note` | `id`, `text`, `tone?` — a dashed callout of wrapping prose |
129
+ | any node | `minW?`/`minH?` (floors), `maxW?`/`maxH?` (caps), `w?`/`h?` (exact), `fill?`, `span?`, `tone?`; groups also `bare?` |
130
+ | `Edge` | `from`, `to` (a node id; a table row `table.row`; a code line `code.L12`; a tree entry `tree.src/a.py`), `label?`, `dashed?`, `bidirectional?` |
131
+ | `Scene` | `nodes`, `edges?`, `title?`, `sub?`, `dir?`, `pulse?` |
132
+
133
+ **Layout.** A group lays its children out as `dir: 'col'` (default), `'row'` (one column per child)
134
+ or `'grid'` (`cols`, default 2). `span: 'all'` gives a child a full row — a rail under columns.
135
+ Siblings in a row stretch to equal height.
136
+
137
+ **Tone** sets what a box means and is inherited by everything inside it:
138
+
139
+ | tone | hue | | tone | hue |
140
+ |---|---|---|---|---|
141
+ | `neutral` | grey | | `serve` | violet |
142
+ | `source` | slate | | `consume` | red |
143
+ | `ingest` | blue | | `infra` | orange |
144
+ | `store` | green | | | |
145
+
146
+ The theme (`dark` | `light`) picks the shade; repos cannot recolour.
147
+
148
+ **Icons** share one key space across four sets; an unknown key draws no icon. Keys match
149
+ `@graphlearning/flow`'s where both have the icon, so scenes port without renaming.
150
+
151
+ | set | drawn in | keys |
152
+ |---|---|---|
153
+ | generic (lucide) | the tone accent | `activity admin bell box branch building card chart check clock cloud code cpu database disk factory file filter fingerprint folder globe key layers lock network package pie screen search server settings shield stream table terminal user users workflow zap` |
154
+ | Azure | its own colours | `adls aks analysisservices azuresql cosmos databricks datafactory defender devops eventhubs functions keyvault loganalytics monitor powerbi purview servicebus storage streamanalytics synapse vm` |
155
+ | AWS | its own tile | `apigateway athena aurora cloudformation cloudwatch dms dynamodb ec2 eks elasticache emr eventbridge firehose glue iam kinesis kms lakeformation lambda msk mwaa rds redshift s3 sagemaker secretsmanager sns sqs stepfunctions vpc` |
156
+ | brand | its own colour (ink if it would vanish) | `airflow docker duckdb flink git github githubactions grafana jupyter kafka kubernetes mongodb pandas postgresql prometheus python snowflakedb spark terraform` |
157
+
158
+ **`chip: true`** draws an item as a small framed pill — a thing counted, not described (`Task 1`,
159
+ `Local disk`); chips fill their grid cell, so a row of them lines up.
160
+
161
+ A nested group is a plain card, unless it names its OWN `tone` — then it is a coloured column again
162
+ (the Spark study's four columns, and its green worker nodes inside the orange Worker column).
163
+
164
+ **`stack: true`** puts an item's icon on top with the text centred under it — a card in a column of
165
+ peers (the Business Consumers column).
package/dist/Code.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import type { CSSProperties } from 'react';
2
+ import type { Code } from './types';
3
+ type Row = {
4
+ n: number;
5
+ text: string;
6
+ } | {
7
+ fold: number;
8
+ };
9
+ /**
10
+ * The lines a `code` node draws: every line, or only the `show` ranges with each skipped run
11
+ * folded into one `··· N lines` row. Line numbers are the FILE's. Shared with the edge planner via
12
+ * `codeLineIds`, so a line edges can address is a line that is drawn.
13
+ */
14
+ export declare function codeRows(node: Code): Row[];
15
+ export declare const codeLineIds: (node: Code) => string[];
16
+ export declare function CodeView({ node, style }: {
17
+ node: Code;
18
+ style?: CSSProperties;
19
+ }): import("react").JSX.Element;
20
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { Routed } from './routing';
2
+ import type { Size } from './fit';
3
+ /**
4
+ * The pulse period, in seconds. A RECORDER CONTRACT: ui-shell's record-course.mjs / record-reels.mjs
5
+ * loop every video on PULSE_S = 2.4. Change it here alone and every loop join in every recorded video
6
+ * shows a jump. Same value, same linear motion as @graphlearning/flow's FlowEdge.
7
+ */
8
+ export declare const PULSE_S = 2.4;
9
+ /** Draws routed edges over the content, in the content's own (unscaled) coordinates. */
10
+ export declare function Edges({ routed, size, pulse }: {
11
+ routed: Routed[];
12
+ size: Size;
13
+ pulse: boolean;
14
+ }): import("react").JSX.Element;
package/dist/Plot.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { Theme } from './theme';
2
+ import type { Plot } from './types';
3
+ export declare const PLOT_W = 720;
4
+ export declare const PLOT_H = 400;
5
+ /** The second fixed size: a plot for a side pane, readable at full text size in ~560px. */
6
+ export declare const COMPACT_W = 420;
7
+ export declare const COMPACT_H = 280;
8
+ export declare function PlotView({ node, theme, style }: {
9
+ node: Plot;
10
+ theme: Theme;
11
+ style?: React.CSSProperties;
12
+ }): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import type { Scene as SceneModel, ThemeKey } from './types';
2
+ /**
3
+ * `focus` names one node (any id, a table row or a code line included) to single out: it gets a
4
+ * ring and a glow, drawn with box-shadow so the layout — and therefore the fit — never moves. That is
5
+ * what lets every section of a course focus a different node on the same scene without a jump.
6
+ *
7
+ * Fills its parent (give the parent a size) and scales the scene's natural box to fit, centred.
8
+ * The viewport is locked: no wheel or drag handlers, so page scroll passes through and text stays
9
+ * selectable. `data-fit="ready"` marks the first fit after fonts load with edges routed — what a recorder waits on.
10
+ */
11
+ export declare function Scene({ scene, theme: key, focus }: {
12
+ scene: SceneModel;
13
+ theme?: ThemeKey;
14
+ focus?: string;
15
+ }): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import type { CSSProperties } from 'react';
2
+ import type { Theme } from './theme';
3
+ import type { Table, TableRow } from './types';
4
+ /**
5
+ * Normalised rows with their ADDRESS: explicit `id`, else the first cell when it is a plain word
6
+ * and unique in the table. Shared by the renderer (data-id) and the edge planner (parent links),
7
+ * so the two cannot disagree about which rows exist.
8
+ */
9
+ export declare function tableRows(table: Table): (TableRow & {
10
+ address?: string;
11
+ })[];
12
+ export declare function TableView({ node, theme, style }: {
13
+ node: Table;
14
+ theme: Theme;
15
+ style?: CSSProperties;
16
+ }): import("react").JSX.Element;
package/dist/Tree.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { CSSProperties } from 'react';
2
+ import type { Theme } from './theme';
3
+ import type { Tree } from './types';
4
+ export declare const treeEntryIds: (node: Tree) => string[];
5
+ export declare function TreeView({ node, theme, style }: {
6
+ node: Tree;
7
+ theme: Theme;
8
+ style?: CSSProperties;
9
+ }): import("react").JSX.Element;
package/dist/fit.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ export interface Size {
2
+ w: number;
3
+ h: number;
4
+ }
5
+ export interface Fit {
6
+ scale: number;
7
+ x: number;
8
+ y: number;
9
+ }
10
+ /** Margin on each side, as a fraction of the pane's own axis. */
11
+ export declare const FIT_PADDING = 0.04;
12
+ export declare const MIN_SCALE = 0.05;
13
+ /** A small scene stops growing here, so it still reads as a diagram, not a poster. */
14
+ export declare const MAX_SCALE = 4;
15
+ /**
16
+ * The fit is a PURE function of the pane and the content's natural size — no async, no history.
17
+ * That is what makes recordings reproducible: two frames whose scenes have the same natural box get
18
+ * the same scale and put every panel at the same pixels.
19
+ */
20
+ export declare function fitTransform(pane: Size, content: Size): Fit;
@@ -0,0 +1,9 @@
1
+ export type TokClass = 'var' | 'method' | 'keyword' | 'string' | 'number' | 'punct' | 'ws' | 'comment';
2
+ export interface Tok {
3
+ cls: TokClass;
4
+ text: string;
5
+ }
6
+ export type Lang = 'python' | 'sql' | 'scala' | 'java' | 'js' | 'ts' | 'shell' | 'text';
7
+ export declare function tokenize(src: string, lang: Lang): Tok[];
8
+ /** Drop a leading/trailing blank line and the common indent, so scenes can use indented template literals. */
9
+ export declare function dedent(src: string): string[];
@@ -0,0 +1,25 @@
1
+ import type { ComponentType, ReactElement } from 'react';
2
+ import { type LucideIcon } from 'lucide-react';
3
+ /** Generic glyphs, drawn in the node's tone accent. */
4
+ export declare const LUCIDE: Record<string, LucideIcon>;
5
+ /** Azure service tiles, in their own colours. The package ships no viewBox; 0 0 18 18 fits all. */
6
+ export declare const AZURE: Record<string, ComponentType<{
7
+ size?: string;
8
+ viewBox?: string;
9
+ }>>;
10
+ /** AWS architecture icons, in their own colours on their own rounded tile. */
11
+ export declare const AWS: Record<string, ComponentType<{
12
+ size?: number | string;
13
+ }>>;
14
+ /** Open-source / product marks (simple-icons), in the brand's colour unless it would vanish. */
15
+ export declare const BRAND: Record<string, {
16
+ title: string;
17
+ hex: string;
18
+ path: string;
19
+ }>;
20
+ /** `size` is a CSS length — in em, so an icon scales WITH its text and the ratio stays designed. */
21
+ type Draw = (size: string, surface: string, ink: string) => ReactElement;
22
+ /** A brand mark under this contrast against the surface draws in ink (Kafka's black on dark). */
23
+ export declare const BRAND_MIN_CONTRAST = 2.2;
24
+ export declare const ICONS: Record<string, Draw>;
25
+ export {};
@@ -0,0 +1,2 @@
1
+ export { Scene } from './Scene';
2
+ export type { Scene as SceneModel, SceneNode, Group, Item, Table, TableRow, Code, Plot, PlotAxis, PlotSeries, Tree, TreeEntry, Note, Edge, Tone, ThemeKey } from './types';