aw-spec-cli 0.1.3 → 0.1.4

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.
Files changed (52) hide show
  1. package/README.md +291 -0
  2. package/assets/docs/UI.Visualization/PACKAGING.md +191 -0
  3. package/assets/docs/UI.Visualization/UI.Visualization.Reference.pack.txt +11768 -0
  4. package/assets/docs/UI.Visualization/UI.Visualization.pack.txt +11214 -0
  5. package/assets/docs/UI.Visualization/pack-foundation.ps1 +165 -0
  6. package/assets/docs/UI.Visualization/pack-foundation.sh +206 -0
  7. package/assets/docs/UI.Visualization/pack-reference-foundation.ps1 +159 -0
  8. package/assets/docs/UI.Visualization/pack-reference-foundation.sh +205 -0
  9. package/assets/docs/UI.Visualization/unpack-foundation.ps1 +126 -0
  10. package/assets/docs/UI.Visualization/unpack-foundation.sh +217 -0
  11. package/assets/docs/UI.Visualization/unpack-reference-foundation.ps1 +129 -0
  12. package/assets/docs/UI.Visualization/unpack-reference-foundation.sh +218 -0
  13. package/assets/docs/domainspecs/UIVisualization/chart-bubble.md +70 -0
  14. package/assets/docs/domainspecs/UIVisualization/chart-dimred.md +58 -0
  15. package/assets/docs/domainspecs/UIVisualization/chart-heatmap.md +62 -0
  16. package/assets/docs/domainspecs/UIVisualization/chart-network.md +69 -0
  17. package/assets/docs/domainspecs/UIVisualization/chart-parallel.md +75 -0
  18. package/assets/docs/domainspecs/UIVisualization/chart-scurve.md +66 -0
  19. package/assets/docs/domainspecs/UIVisualization/chart-survival.md +69 -0
  20. package/assets/docs/domainspecs/UIVisualization/chart-trellis.md +68 -0
  21. package/assets/docs/domainspecs/UIVisualization/chart-violin.md +61 -0
  22. package/assets/docs/domainspecs/UIVisualization/chart-volcano.md +63 -0
  23. package/assets/docs/domainspecs/UIVisualization/charting-standards.domain.md +80 -0
  24. package/assets/docs/domainspecs/UIVisualization/clean-architecture.domain.md +87 -0
  25. package/assets/docs/domainspecs/UIVisualization/coding-standards.domain.md +47 -0
  26. package/assets/docs/domainspecs/UIVisualization/comandrulemap.md +130 -0
  27. package/assets/docs/domainspecs/UIVisualization/component-contracts.domain.md +67 -0
  28. package/assets/docs/domainspecs/UIVisualization/data-source.domain.md +68 -0
  29. package/assets/docs/domainspecs/UIVisualization/domain-requirements.md +110 -0
  30. package/assets/docs/domainspecs/UIVisualization/folder-structure.domain.md +55 -0
  31. package/assets/docs/domainspecs/UIVisualization/state-management.domain.md +67 -0
  32. package/assets/docs/domainspecs/UIVisualization/testing.domain.md +52 -0
  33. package/assets/docs/domainspecs/UIVisualization/ui-viz-terms.md +39 -0
  34. package/dist/commands/init.d.ts.map +1 -1
  35. package/dist/commands/init.js +54 -7
  36. package/dist/commands/init.js.map +1 -1
  37. package/dist/commands/update.d.ts +3 -0
  38. package/dist/commands/update.d.ts.map +1 -0
  39. package/dist/commands/update.js +142 -0
  40. package/dist/commands/update.js.map +1 -0
  41. package/dist/index.js +2 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/utils/manifest.d.ts +31 -0
  44. package/dist/utils/manifest.d.ts.map +1 -0
  45. package/dist/utils/manifest.js +91 -0
  46. package/dist/utils/manifest.js.map +1 -0
  47. package/dist/utils/update-engine.d.ts +52 -0
  48. package/dist/utils/update-engine.d.ts.map +1 -0
  49. package/dist/utils/update-engine.js +203 -0
  50. package/dist/utils/update-engine.js.map +1 -0
  51. package/package.json +1 -1
  52. package/assets/docs/requirement-spec.txt +0 -15
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # unpack-reference-foundation.sh — bash/macOS port of unpack-reference-foundation.ps1
4
+ #
5
+ # SYNOPSIS
6
+ # Unpack an AW-Spec reference pack file into a target project, renaming the
7
+ # sentinel app name to the caller's app name.
8
+ #
9
+ # DESCRIPTION
10
+ # Reads --pack-file (produced by pack-reference-foundation.sh/.ps1), takes the sentinel
11
+ # from its header (override with --sentinel), derives four case forms of the target app
12
+ # name, and writes every file block to --out-dir with the sentinel rewritten in both file
13
+ # CONTENTS and file/dir PATHS. Text blocks are rewritten and written as UTF-8; base64
14
+ # blocks are decoded as-is. Refuses to overwrite existing files unless --force.
15
+ #
16
+ # EXAMPLE
17
+ # ./unpack-reference-foundation.sh \
18
+ # --pack-file ./UI.Visualization.Reference.pack.txt \
19
+ # --target-app-name MyApp \
20
+ # --out-dir ../../
21
+ #
22
+ set -euo pipefail
23
+
24
+ # ------------------------------------------------------------------ defaults ---
25
+ PACK_FILE=''
26
+ TARGET_APP_NAME=''
27
+ OUT_DIR='.'
28
+ SENTINEL=''
29
+ FORCE=0
30
+ DRYRUN=0
31
+
32
+ usage() {
33
+ cat >&2 <<'EOF'
34
+ Usage: unpack-reference-foundation.sh --pack-file <file> --target-app-name <name>
35
+ [--out-dir <dir>] [--sentinel <name>]
36
+ [--force] [--dry-run]
37
+
38
+ -p, --pack-file <file> Reference pack file to unpack (required)
39
+ -t, --target-app-name <name> New app name to rename the sentinel into (required)
40
+ -o, --out-dir <dir> Output root (default: .)
41
+ --sentinel <name> Override the sentinel from the pack header
42
+ --force Overwrite files that already exist
43
+ --dry-run List what would be written, write nothing
44
+ -h, --help Show this help
45
+
46
+ Positional fallback: unpack-reference-foundation.sh <pack-file> <target-app-name> [out-dir]
47
+ EOF
48
+ }
49
+
50
+ # -------------------------------------------------------------- arg parsing ---
51
+ while [ $# -gt 0 ]; do
52
+ case "$1" in
53
+ -p|--pack-file) PACK_FILE="${2:?--pack-file needs a value}"; shift 2 ;;
54
+ -t|--target-app-name) TARGET_APP_NAME="${2:?--target-app-name needs a value}"; shift 2 ;;
55
+ -o|--out-dir) OUT_DIR="${2:?--out-dir needs a value}"; shift 2 ;;
56
+ --sentinel) SENTINEL="${2:?--sentinel needs a value}"; shift 2 ;;
57
+ --force) FORCE=1; shift ;;
58
+ --dry-run) DRYRUN=1; shift ;;
59
+ -h|--help) usage; exit 0 ;;
60
+ --) shift; break ;;
61
+ -*) echo "Error: unknown option: $1" >&2; usage; exit 2 ;;
62
+ *)
63
+ if [ -z "$PACK_FILE" ]; then PACK_FILE="$1"
64
+ elif [ -z "$TARGET_APP_NAME" ]; then TARGET_APP_NAME="$1"
65
+ else OUT_DIR="$1"
66
+ fi
67
+ shift ;;
68
+ esac
69
+ done
70
+
71
+ [ -n "$PACK_FILE" ] || { echo "Error: --pack-file is required." >&2; usage; exit 2; }
72
+ [ -n "$TARGET_APP_NAME" ] || { echo "Error: --target-app-name is required." >&2; usage; exit 2; }
73
+ [ -f "$PACK_FILE" ] || { echo "Error: pack file not found: $PACK_FILE" >&2; exit 1; }
74
+
75
+ case "$TARGET_APP_NAME" in
76
+ *[!A-Za-z0-9._-]*)
77
+ echo "Error: --target-app-name may contain only letters, digits, '.', '_' or '-': $TARGET_APP_NAME" >&2
78
+ exit 2 ;;
79
+ esac
80
+
81
+ # ------------------------------------------------ sentinel from header --------
82
+ if [ -z "$SENTINEL" ]; then
83
+ SENTINEL="$(LC_ALL=C sed -n 's/^#[[:space:]]*sentinel:[[:space:]]*\(.*[^[:space:]]\)[[:space:]]*$/\1/p' "$PACK_FILE" | head -n1)"
84
+ fi
85
+ [ -n "$SENTINEL" ] || { echo "Error: Sentinel not found in pack header; supply it with --sentinel." >&2; exit 1; }
86
+
87
+ # ------------------------------------------------ derive case forms -----------
88
+ to_lower() { printf '%s' "$1" | LC_ALL=C tr '[:upper:]' '[:lower:]'; }
89
+
90
+ S_DOT="$SENTINEL"; T_DOT="$TARGET_APP_NAME"
91
+ S_UND="${SENTINEL//./_}"; T_UND="${TARGET_APP_NAME//./_}"
92
+ s_low="$(to_lower "$SENTINEL")"; t_low="$(to_lower "$TARGET_APP_NAME")"
93
+ S_HYP="${s_low//./-}"; T_HYP="${t_low//./-}"
94
+ S_LDOT="$s_low"; T_LDOT="$t_low"
95
+
96
+ FROM=(); TO=()
97
+ add_form() {
98
+ local from="$1" to="$2" e
99
+ [ -n "$from" ] || return 0
100
+ [ "$from" = "$to" ] && return 0
101
+ if [ "${#FROM[@]}" -gt 0 ]; then
102
+ for e in "${FROM[@]}"; do [ "$e" = "$from" ] && return 0; done
103
+ fi
104
+ FROM+=("$from"); TO+=("$to")
105
+ }
106
+ add_form "$S_DOT" "$T_DOT"
107
+ add_form "$S_UND" "$T_UND"
108
+ add_form "$S_HYP" "$T_HYP"
109
+ add_form "$S_LDOT" "$T_LDOT"
110
+
111
+ # Sort indices by FROM length descending
112
+ ORDER=()
113
+ {
114
+ local_n="${#FROM[@]}"
115
+ used=(); for ((k = 0; k < local_n; k++)); do used[$k]=0; done
116
+ for ((pass = 0; pass < local_n; pass++)); do
117
+ best=-1; bestlen=-1
118
+ for ((j = 0; j < local_n; j++)); do
119
+ if [ "${used[$j]}" -eq 0 ] && [ "${#FROM[$j]}" -gt "$bestlen" ]; then
120
+ bestlen="${#FROM[$j]}"; best="$j"
121
+ fi
122
+ done
123
+ used[$best]=1; ORDER+=("$best")
124
+ done
125
+ }
126
+
127
+ REPL_OUT=''
128
+ apply_repl() {
129
+ local text="$1" idx from to
130
+ if [ "${#ORDER[@]}" -gt 0 ]; then
131
+ for idx in "${ORDER[@]}"; do
132
+ from="${FROM[$idx]}"; to="${TO[$idx]}"
133
+ text="${text//"$from"/$to}"
134
+ done
135
+ fi
136
+ REPL_OUT="$text"
137
+ }
138
+
139
+ # ------------------------------------------------ output root ----------------
140
+ [ -d "$OUT_DIR" ] || mkdir -p "$OUT_DIR"
141
+ OUT_ROOT="$(cd "$OUT_DIR" && pwd -P)"
142
+
143
+ # ------------------------------------------------ base64 decoder (portable) --
144
+ B64D=()
145
+ if printf 'aGk=' | base64 -d >/dev/null 2>&1; then B64D=(base64 -d)
146
+ elif printf 'aGk=' | base64 -D >/dev/null 2>&1; then B64D=(base64 -D)
147
+ elif printf 'aGk=' | base64 --decode >/dev/null 2>&1; then B64D=(base64 --decode)
148
+ else B64D=(openssl base64 -d -A)
149
+ fi
150
+
151
+ # ------------------------------------------------ parse file blocks ----------
152
+ rels=(); encs=(); contents=()
153
+ in_block=0; cur_rel=''; cur_enc=''; cur_content=''; first_line=1
154
+ file_re='^<<<FILE path="([^"]+)"( encoding="([^"]+)")?>>>[[:space:]]*$'
155
+
156
+ while IFS= read -r line || [ -n "$line" ]; do
157
+ line="${line%$'\r'}"
158
+ if [ "$in_block" -eq 0 ]; then
159
+ if [[ "$line" =~ $file_re ]]; then
160
+ cur_rel="${BASH_REMATCH[1]}"
161
+ cur_enc="${BASH_REMATCH[3]:-}"
162
+ in_block=1; first_line=1; cur_content=''
163
+ fi
164
+ else
165
+ if [ "$line" = '<<<ENDFILE>>>' ]; then
166
+ rels+=("$cur_rel"); encs+=("$cur_enc"); contents+=("$cur_content")
167
+ in_block=0
168
+ elif [ "$first_line" -eq 1 ]; then
169
+ cur_content="$line"; first_line=0
170
+ else
171
+ cur_content="$cur_content"$'\n'"$line"
172
+ fi
173
+ fi
174
+ done < "$PACK_FILE"
175
+
176
+ [ "${#rels[@]}" -gt 0 ] || { echo "Error: No <<<FILE>>> blocks found in pack: $PACK_FILE" >&2; exit 1; }
177
+
178
+ # ------------------------------------------------ resolve destinations -------
179
+ dests=()
180
+ for ((i = 0; i < ${#rels[@]}; i++)); do
181
+ apply_repl "${rels[$i]}"
182
+ dests+=("$OUT_ROOT/$REPL_OUT")
183
+ done
184
+
185
+ # ------------------------------------------------ conflict guard -------------
186
+ if [ "$FORCE" -eq 0 ] && [ "$DRYRUN" -eq 0 ]; then
187
+ conflicts=()
188
+ for d in "${dests[@]}"; do [ -e "$d" ] && conflicts+=("$d"); done
189
+ if [ "${#conflicts[@]}" -gt 0 ]; then
190
+ echo "Error: Refusing to overwrite existing files (re-run with --force):" >&2
191
+ printf ' %s\n' "${conflicts[@]}" >&2
192
+ exit 1
193
+ fi
194
+ fi
195
+
196
+ # ------------------------------------------------ write ----------------------
197
+ written=0
198
+ for ((i = 0; i < ${#rels[@]}; i++)); do
199
+ rel="${rels[$i]}"; enc="${encs[$i]}"; dest="${dests[$i]}"
200
+ if [ "$DRYRUN" -eq 1 ]; then
201
+ echo "WOULD WRITE $rel -> $dest"
202
+ continue
203
+ fi
204
+ mkdir -p "$(dirname "$dest")"
205
+ if [ "$enc" = 'base64' ]; then
206
+ printf '%s' "${contents[$i]}" | tr -d '[:space:]' | "${B64D[@]}" > "$dest"
207
+ else
208
+ apply_repl "${contents[$i]}"
209
+ printf '%s' "$REPL_OUT" > "$dest"
210
+ fi
211
+ written=$((written + 1))
212
+ done
213
+
214
+ if [ "$DRYRUN" -eq 1 ]; then
215
+ echo "DryRun: ${#rels[@]} file(s) would be written to $OUT_ROOT (sentinel '$SENTINEL' -> '$TARGET_APP_NAME')."
216
+ else
217
+ echo "Unpacked $written file(s) to $OUT_ROOT (sentinel '$SENTINEL' -> '$TARGET_APP_NAME')."
218
+ fi
@@ -0,0 +1,70 @@
1
+ # BubbleChart — Chart Rule
2
+
3
+ > Load **only when "Bubble" (or "all plots") is in the selection** (see [[domain-requirements]]).
4
+ > Component: `BubbleChart` in `components/charts/generic/BubbleChart.tsx`. Built on [[charting-standards.domain]].
5
+
6
+ ## Purpose
7
+ Multi-dimensional scatter — each point encodes **x, y, size, colour, label** (up to 5 dimensions).
8
+ Use for potency vs MW, portfolio risk/return, effort vs impact, market mapping.
9
+
10
+ ## Props contract
11
+ ```ts
12
+ interface BubblePoint { x: number; y: number; size: number; label: string; meta?: Record<string, string|number> }
13
+ interface BubbleSeries { seriesLabel: string; color?: string; points: BubblePoint[] }
14
+
15
+ interface BubbleChartProps {
16
+ series: BubbleSeries[] // REQUIRED — one series per colour/category
17
+ xLabel: string // REQUIRED
18
+ yLabel: string // REQUIRED
19
+ sizeLabel?: string // shown as "Bubble size ∝ …" annotation
20
+ title?: string
21
+ xAxisType?: 'linear' | 'log'
22
+ yAxisType?: 'linear' | 'log'
23
+ sizeRange?: [number, number] // [minPx, maxPx] bubble diameter (default [8,40])
24
+ xRefLine?: number; xRefLabel?: string // optional vertical reference line
25
+ yRefLine?: number; yRefLabel?: string // optional horizontal reference line
26
+ height?: number
27
+ showLabels?: boolean // annotate points with their label
28
+ }
29
+ ```
30
+
31
+ ## Rules
32
+ - Bubble sizes are **normalised across the whole dataset** to `sizeRange` — pass raw magnitudes;
33
+ the chart scales them. Don't pre-scale to pixels.
34
+ - Group points into **series by the categorical dimension** (target, region, product line) so
35
+ colour + legend come for free.
36
+ - Use `xAxisType="log"` / `yAxisType="log"` or pre-`log10` the value (e.g. IC₅₀) — pick one,
37
+ document it in the axis label.
38
+
39
+ ## Adapter example (agent reference)
40
+ ```tsx
41
+ 'use client'
42
+ import { BubbleChart } from '@/components/charts/generic'
43
+ import type { Compound } from '@/lib/types/pharma'
44
+
45
+ export function compoundsToBubbleSeries(compounds: Compound[]) {
46
+ const targets = [...new Set(compounds.map(c => c.target))]
47
+ return targets.map(t => ({
48
+ seriesLabel: t,
49
+ points: compounds.filter(c => c.target === t).map(c => ({
50
+ x: c.molecularWeight, y: Math.log10(c.ic50_nM),
51
+ size: c.selectivityRatio, label: c.name,
52
+ meta: { Status: c.status, 'IC₅₀': `${c.ic50_nM} nM` },
53
+ })),
54
+ }))
55
+ }
56
+
57
+ export default function CompoundScatterPlot({ compounds, height }: { compounds: Compound[]; height?: number }) {
58
+ return (
59
+ <BubbleChart series={compoundsToBubbleSeries(compounds)}
60
+ xLabel="Molecular Weight (Da)" yLabel="log₁₀(IC₅₀, nM)" sizeLabel="Selectivity Ratio"
61
+ xRefLine={500} xRefLabel="Ro5 MW limit" sizeRange={[7, 28]} height={height} />
62
+ )
63
+ }
64
+ ```
65
+
66
+ ## Scaffolds when selected
67
+ - generic `BubbleChart` + `app/bubble/page.tsx` + nav tab + optional adapter.
68
+
69
+ ## Related
70
+ - [[charting-standards.domain]] · [[component-contracts.domain]] · [[clean-architecture.domain]]
@@ -0,0 +1,58 @@
1
+ # DimRedPlot — Chart Rule
2
+
3
+ > Load **only when "Dim. Reduction / PCA / t-SNE / UMAP" (or "all plots") is selected** (see [[domain-requirements]]).
4
+ > Component: `DimRedPlot` in `components/charts/generic/DimRedPlot.tsx`. Built on [[charting-standards.domain]].
5
+
6
+ ## Purpose
7
+ 2-D scatter of **pre-computed** dimensionality-reduction coordinates, coloured by group, with
8
+ optional cluster-centroid labels and density contours. Use for single-cell clustering, customer
9
+ segments, NLP embeddings, chemical-space maps, anomaly scatter.
10
+
11
+ ## Props contract
12
+ ```ts
13
+ interface DimRedPoint {
14
+ x: number; y: number // reduced coordinates (caller ran PCA/t-SNE/UMAP)
15
+ label: string
16
+ group: string // category → colour + legend entry
17
+ meta?: Record<string, string|number>
18
+ }
19
+ interface DimRedPlotProps {
20
+ points: DimRedPoint[] // REQUIRED
21
+ method?: string // badge: "PCA", "t-SNE", "UMAP" (default "DR")
22
+ xLabel?: string; yLabel?: string // e.g. "PC1 (34.2% var)", "UMAP 1"
23
+ colorByLabel?: string // legend header / tooltip ("Cell type", "Segment")
24
+ showClusterLabels?: boolean // centroid label per group (default true)
25
+ showDensityContours?: boolean // KDE contour per group (default false)
26
+ markerSize?: number // default 5
27
+ title?: string; height?: number
28
+ }
29
+ ```
30
+
31
+ ## Rules
32
+ - The chart **does not run** PCA/t-SNE/UMAP — coordinates are computed upstream (Python,
33
+ backend, or a JS lib) and passed in. Document the method in `method` + axis labels.
34
+ - `group` drives colour and the legend; keep group count modest (≤ ~12) for distinct colours.
35
+ - Turn on `showDensityContours` for large, overlapping point clouds; off for small datasets.
36
+
37
+ ## Page/adapter example (agent reference)
38
+ ```tsx
39
+ 'use client'
40
+ import { DimRedPlot } from '@/components/charts/generic'
41
+
42
+ // cells already have umap1/umap2 + cellType from the pipeline
43
+ export default function CellUmap({ cells }: { cells: { id: string; umap1: number; umap2: number; cellType: string }[] }) {
44
+ return (
45
+ <DimRedPlot
46
+ points={cells.map(c => ({ x: c.umap1, y: c.umap2, label: c.id, group: c.cellType }))}
47
+ method="UMAP" xLabel="UMAP 1" yLabel="UMAP 2" colorByLabel="Cell type" showClusterLabels
48
+ />
49
+ )
50
+ }
51
+ ```
52
+
53
+ ## Scaffolds when selected
54
+ - generic `DimRedPlot` + `app/dimred/page.tsx` + nav tab + optional adapter (+ a place to compute
55
+ or fetch coordinates).
56
+
57
+ ## Related
58
+ - [[charting-standards.domain]] · [[component-contracts.domain]] · [[clean-architecture.domain]]
@@ -0,0 +1,62 @@
1
+ # Heatmap — Chart Rule
2
+
3
+ > Load **only when "Heatmap" (or "all plots") is in the selection** (see [[domain-requirements]]).
4
+ > Component: `Heatmap` in `components/charts/generic/Heatmap.tsx`. Built on [[charting-standards.domain]].
5
+
6
+ ## Purpose
7
+ Colour-encoded grid for a 2-D value matrix. Use for correlation matrices, expression grids
8
+ (log₂FC), confusion matrices, risk tables, cross-asset return grids.
9
+
10
+ ## Props contract
11
+ ```ts
12
+ type ColourPreset = 'diverging-purple-red' | 'sequential-teal' | 'sequential-heat' | 'diverging-blue-red'
13
+
14
+ interface HeatmapProps {
15
+ matrix: number[][] // matrix[row][col] — REQUIRED
16
+ rowLabels: string[] // y-axis — REQUIRED
17
+ colLabels: string[] // x-axis — REQUIRED
18
+ title?: string
19
+ unit?: string // colour-bar title + tooltip unit (e.g. "log₂FC", "r")
20
+ colorscale?: ColourPreset | [number, string][] // default 'diverging-purple-red'
21
+ zMid?: number // centre of a diverging scale (use 0 for FC / correlation)
22
+ zMin?: number; zMax?: number // override colour range (defaults to matrix min/max)
23
+ showCellValues?: boolean // annotate each cell (default true)
24
+ cellValuePrecision?: number // decimals in annotations (default 2)
25
+ cellValueContrastThreshold?: number // |value| above → white text (default 1.0)
26
+ height?: number // omit for responsive fill
27
+ reverseRows?: boolean
28
+ }
29
+ ```
30
+
31
+ ## Rules
32
+ - `matrix.length === rowLabels.length`; every row length `=== colLabels.length`. Validate in the
33
+ adapter, not the chart.
34
+ - For **diverging** data (fold-change, correlation) set `zMid={0}` so zero maps to the neutral
35
+ midpoint; for **sequential** data (counts, intensity) use a `sequential-*` preset and omit `zMid`.
36
+ - Keep cell annotations on for ≤ ~12×12 grids; turn `showCellValues` off for dense matrices.
37
+
38
+ ## Adapter example (agent reference)
39
+ ```tsx
40
+ 'use client'
41
+ import { Heatmap } from '@/components/charts/generic'
42
+ import type { BiomarkerData } from '@/lib/types/pharma'
43
+
44
+ export default function BiomarkerHeatmap({ data, height }: { data: BiomarkerData; height?: number }) {
45
+ return (
46
+ <Heatmap
47
+ matrix={data.matrix} rowLabels={data.treatments} colLabels={data.markers}
48
+ unit="log₂FC" colorscale="diverging-purple-red" zMid={0}
49
+ showCellValues cellValuePrecision={2} cellValueContrastThreshold={1.5}
50
+ height={height}
51
+ />
52
+ )
53
+ }
54
+ ```
55
+
56
+ ## Scaffolds when selected
57
+ - `components/charts/generic/Heatmap.tsx` (from the generic library — always present)
58
+ - `app/heatmap/page.tsx` (example route) + nav tab
59
+ - optional adapter `components/charts/<Domain>Heatmap.tsx`
60
+
61
+ ## Related
62
+ - [[charting-standards.domain]] · [[component-contracts.domain]] · [[clean-architecture.domain]]
@@ -0,0 +1,69 @@
1
+ # NetworkGraph — Chart Rule
2
+
3
+ > Load **only when "Network / graph / node-edge" (or "all plots") is selected** (see [[domain-requirements]]).
4
+ > Component: `NetworkGraph` in `components/charts/generic/NetworkGraph.tsx`. Built on [[charting-standards.domain]].
5
+
6
+ ## Purpose
7
+ Force-directed **node-edge** graph rendered with Plotly scatter+line traces; layout is computed
8
+ in-component (Fruchterman-Reingold spring or circular) so **no extra graph library** is needed.
9
+ Use for PPI/pathway networks, microservice/dependency maps, knowledge graphs, org/social graphs.
10
+
11
+ ## Props contract
12
+ ```ts
13
+ interface NetworkNode {
14
+ id: string // referenced by edges
15
+ label: string
16
+ group?: string // colour + legend
17
+ size?: number // marker size (importance, default 10)
18
+ meta?: Record<string, string|number>
19
+ }
20
+ interface NetworkEdge {
21
+ source: string; target: string // node ids
22
+ weight?: number // line thickness (default 1)
23
+ label?: string // hover label
24
+ }
25
+ interface NetworkGraphProps {
26
+ nodes: NetworkNode[]; edges: NetworkEdge[] // REQUIRED
27
+ layout?: 'spring' | 'circular' // default 'spring'
28
+ springIterations?: number // default 120 (higher = better layout, slower)
29
+ showEdgeLabels?: boolean // default true
30
+ directed?: boolean // arrowheads (default false)
31
+ title?: string; height?: number
32
+ }
33
+ ```
34
+
35
+ ## Rules
36
+ - Every edge `source`/`target` MUST reference an existing node `id`. Validate in the adapter;
37
+ dangling edges are dropped silently.
38
+ - Layout is **deterministic per dataset** (warm-started from a circle) but recomputes when the
39
+ node/edge set changes — memoise `nodes`/`edges` so it doesn't recompute each render.
40
+ - Keep graphs modest (≲ ~150 nodes) for readable spring layout; above that, pre-cluster or paginate.
41
+ - Set `directed` for DAGs / signalling cascades; leave off for undirected association graphs.
42
+
43
+ ## Page/adapter example (agent reference)
44
+ ```tsx
45
+ 'use client'
46
+ import { NetworkGraph } from '@/components/charts/generic'
47
+ import type { NetworkNode, NetworkEdge } from '@/components/charts/generic'
48
+
49
+ const NODES: NetworkNode[] = [
50
+ { id: 'EGFR', label: 'EGFR', group: 'Receptor', size: 26 },
51
+ { id: 'RAS', label: 'RAS', group: 'GTPase', size: 22 },
52
+ { id: 'RAF', label: 'BRAF', group: 'Kinase', size: 20 },
53
+ ]
54
+ const EDGES: NetworkEdge[] = [
55
+ { source: 'EGFR', target: 'RAS', weight: 2.5, label: 'activates' },
56
+ { source: 'RAS', target: 'RAF', weight: 2, label: 'activates' },
57
+ ]
58
+
59
+ export default function PathwayNetwork() {
60
+ return <NetworkGraph nodes={NODES} edges={EDGES} layout="spring" springIterations={150} directed showEdgeLabels />
61
+ }
62
+ ```
63
+
64
+ ## Scaffolds when selected
65
+ - generic `NetworkGraph` + `app/network/page.tsx` + nav tab + optional adapter; type the node/edge
66
+ arrays with `NetworkNode[]` / `NetworkEdge[]` to avoid `meta` literal-type errors.
67
+
68
+ ## Related
69
+ - [[charting-standards.domain]] · [[component-contracts.domain]] · [[clean-architecture.domain]]
@@ -0,0 +1,75 @@
1
+ # ParallelCoordinatesPlot — Chart Rule
2
+
3
+ > Load **only when "Parallel Coords / parcoords" (or "all plots") is selected** (see [[domain-requirements]]).
4
+ > Component: `ParallelCoordinatesPlot` in `components/charts/generic/ParallelCoordinatesPlot.tsx`.
5
+ > Built on [[charting-standards.domain]]; uses Plotly's native `parcoords` (interactive axis brushing).
6
+
7
+ ## Purpose
8
+ Multi-dimensional data as **polylines crossing parallel vertical axes** — one line per observation.
9
+ Use for multi-parameter optimisation (MPO), ADMET profiles, hyperparameter sweeps, KPI dashboards,
10
+ portfolio metrics.
11
+
12
+ ## Props contract
13
+ ```ts
14
+ interface PCDimension {
15
+ key: string // matches a key in each trace's `values`
16
+ label: string
17
+ range?: [number, number] // axis range (auto if omitted)
18
+ tickFormat?: string // d3 format, e.g. ".2f", ".0f"
19
+ invertAxis?: boolean // for "lower is better" metrics (IC₅₀, p-value)
20
+ }
21
+ interface PCTrace {
22
+ id: string; label: string
23
+ colorValue: number // drives the line colour (continuous)
24
+ values: Record<string, number> // one value per dimension key
25
+ group?: string
26
+ }
27
+ type PCColorscale = 'teal-purple' | 'blue-red' | 'viridis' | 'plasma' | [number, string][]
28
+
29
+ interface ParallelCoordinatesPlotProps {
30
+ dimensions: PCDimension[] // REQUIRED — the axes
31
+ traces: PCTrace[] // REQUIRED — one per observation
32
+ colorLabel?: string // colour-bar title
33
+ colorscale?: PCColorscale // default 'teal-purple'
34
+ lineOpacity?: number; lineWidth?: number
35
+ title?: string; height?: number
36
+ }
37
+ ```
38
+
39
+ ## Rules
40
+ - `dimensions[].key` must exist in **every** trace's `values`. Validate in the adapter.
41
+ - Use `invertAxis` for metrics where **lower is better** so "good" lines trend the same way.
42
+ - `colorValue` is a single continuous dimension (often the headline metric, e.g. IC₅₀ or score);
43
+ it maps onto `colorscale`. Brushing on any axis filters interactively — no extra wiring.
44
+
45
+ ## Page/adapter example (agent reference)
46
+ ```tsx
47
+ 'use client'
48
+ import { ParallelCoordinatesPlot } from '@/components/charts/generic'
49
+ import type { Compound } from '@/lib/types/pharma'
50
+
51
+ const DIMS = [
52
+ { key: 'mw', label: 'MW (Da)', range: [300, 560] as [number, number], tickFormat: '.0f' },
53
+ { key: 'logP', label: 'LogP', range: [1.5, 5.5] as [number, number], tickFormat: '.1f' },
54
+ { key: 'ic50', label: 'IC₅₀ (nM)', range: [0, 800] as [number, number], tickFormat: '.0f', invertAxis: true },
55
+ ]
56
+
57
+ export default function MpoParallel({ compounds }: { compounds: Compound[] }) {
58
+ return (
59
+ <ParallelCoordinatesPlot
60
+ dimensions={DIMS}
61
+ traces={compounds.filter(c => c.ic50_nM <= 800).map(c => ({
62
+ id: c.id, label: c.name, group: c.status, colorValue: c.ic50_nM,
63
+ values: { mw: c.molecularWeight, logP: c.logP, ic50: c.ic50_nM },
64
+ }))}
65
+ colorLabel="IC₅₀ (nM)" colorscale="teal-purple"
66
+ />
67
+ )
68
+ }
69
+ ```
70
+
71
+ ## Scaffolds when selected
72
+ - generic `ParallelCoordinatesPlot` + `app/parallel/page.tsx` + nav tab + optional adapter.
73
+
74
+ ## Related
75
+ - [[charting-standards.domain]] · [[component-contracts.domain]] · [[clean-architecture.domain]]
@@ -0,0 +1,66 @@
1
+ # SCurveChart — Chart Rule
2
+
3
+ > Load **only when "S-Curve / dose-response / sigmoid" (or "all plots") is selected** (see [[domain-requirements]]).
4
+ > Component: `SCurveChart` in `components/charts/generic/SCurveChart.tsx`. Built on [[charting-standards.domain]].
5
+
6
+ ## Purpose
7
+ One or more **sigmoidal (S-shaped)** curves with optional inflection markers. Use for
8
+ dose-response (4-PL / Hill), technology adoption, market penetration, enzyme kinetics, SaaS ramp.
9
+
10
+ ## Props contract
11
+ ```ts
12
+ interface SCurvePoint { x: number; y: number }
13
+ interface SCurve {
14
+ id: string; label: string; color?: string
15
+ points: SCurvePoint[] // PRE-COMPUTED by the caller (model runs outside the chart)
16
+ inflectionX?: number // e.g. IC₅₀ / midpoint — draws a marker + dashed line
17
+ inflectionLabel?: string // "IC₅₀", "EC₅₀", "Midpoint", "Tipping point"
18
+ metadata?: Record<string, string> // tooltip enrichment
19
+ }
20
+ interface SCurveChartProps {
21
+ curves: SCurve[] // REQUIRED
22
+ xLabel: string; yLabel: string // REQUIRED
23
+ xAxisType?: 'linear' | 'log'
24
+ referenceY?: number // horizontal reference (default 50)
25
+ referenceYLabel?: string
26
+ yRange?: [number, number] // default [-5, 108]
27
+ title?: string; height?: number
28
+ }
29
+ ```
30
+
31
+ ## Rules
32
+ - The chart **plots points, it does not fit models** — the caller generates curve points (4-PL,
33
+ logistic, etc.) and passes them. Provide generators in `lib/utils.ts`
34
+ (`generate4PLCurve`, `generateLogisticPoints`).
35
+ - `inflectionX` is in the **same x-units** as the points (e.g. `log10(ic50_nM)` if x is log-conc).
36
+ - Use `referenceY={50}` for "50% response/adoption"; set to `undefined` to hide.
37
+
38
+ ## Adapter example (agent reference)
39
+ ```tsx
40
+ 'use client'
41
+ import { SCurveChart } from '@/components/charts/generic'
42
+ import { PHARMA_COLORS } from '@/components/charts/PlotlyChart'
43
+ import type { DoseResponseCurve } from '@/lib/types/pharma'
44
+
45
+ export default function DoseResponseChart({ curves, height }: { curves: DoseResponseCurve[]; height?: number }) {
46
+ return (
47
+ <SCurveChart
48
+ curves={curves.map((c, i) => ({
49
+ id: c.compoundId, label: c.compoundName, color: PHARMA_COLORS[i % PHARMA_COLORS.length],
50
+ points: c.points.map(p => ({ x: p.logConc, y: p.response })),
51
+ inflectionX: Math.log10(c.ic50_nM), inflectionLabel: 'IC₅₀',
52
+ metadata: { Target: c.target, 'Hill slope': c.hillSlope.toFixed(2) },
53
+ }))}
54
+ xLabel="log₁₀[Concentration (nM)]" yLabel="% Inhibition"
55
+ referenceY={50} referenceYLabel="50% Inhibition" height={height}
56
+ />
57
+ )
58
+ }
59
+ ```
60
+
61
+ ## Scaffolds when selected
62
+ - generic `SCurveChart` + curve generators in `lib/utils.ts` + `app/scurve/` (or `dose-response/`)
63
+ route + nav tab + optional adapter.
64
+
65
+ ## Related
66
+ - [[charting-standards.domain]] · [[component-contracts.domain]] · [[clean-architecture.domain]]