sdr-visualizer 0.6.0__tar.gz

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 (88) hide show
  1. sdr_visualizer-0.6.0/.gitignore +73 -0
  2. sdr_visualizer-0.6.0/CHANGELOG.md +301 -0
  3. sdr_visualizer-0.6.0/CONTRIBUTING.md +59 -0
  4. sdr_visualizer-0.6.0/LICENSE +21 -0
  5. sdr_visualizer-0.6.0/PKG-INFO +164 -0
  6. sdr_visualizer-0.6.0/README.md +148 -0
  7. sdr_visualizer-0.6.0/SECURITY.md +24 -0
  8. sdr_visualizer-0.6.0/THIRD_PARTY_LICENSES +27 -0
  9. sdr_visualizer-0.6.0/docs/ADAPTER_GUIDE.md +105 -0
  10. sdr_visualizer-0.6.0/docs/ARCHITECTURE.md +84 -0
  11. sdr_visualizer-0.6.0/docs/EMBEDDED_DATA_FORMAT.md +177 -0
  12. sdr_visualizer-0.6.0/docs/PERFORMANCE.md +45 -0
  13. sdr_visualizer-0.6.0/pyproject.toml +68 -0
  14. sdr_visualizer-0.6.0/scripts/capture_screenshot.py +60 -0
  15. sdr_visualizer-0.6.0/scripts/corpus_check.py +134 -0
  16. sdr_visualizer-0.6.0/scripts/generate_aa_large_fixture.py +154 -0
  17. sdr_visualizer-0.6.0/scripts/generate_examples.py +42 -0
  18. sdr_visualizer-0.6.0/scripts/generate_large_fixture.py +196 -0
  19. sdr_visualizer-0.6.0/scripts/mutate_fixture.py +97 -0
  20. sdr_visualizer-0.6.0/scripts/perf_browser_check.py +259 -0
  21. sdr_visualizer-0.6.0/scripts/perf_check.py +242 -0
  22. sdr_visualizer-0.6.0/src/sdr_visualizer/__init__.py +3 -0
  23. sdr_visualizer-0.6.0/src/sdr_visualizer/__main__.py +6 -0
  24. sdr_visualizer-0.6.0/src/sdr_visualizer/adapters/__init__.py +0 -0
  25. sdr_visualizer-0.6.0/src/sdr_visualizer/adapters/aa.py +370 -0
  26. sdr_visualizer-0.6.0/src/sdr_visualizer/adapters/base.py +1 -0
  27. sdr_visualizer-0.6.0/src/sdr_visualizer/adapters/cja.py +496 -0
  28. sdr_visualizer-0.6.0/src/sdr_visualizer/analysis/__init__.py +0 -0
  29. sdr_visualizer-0.6.0/src/sdr_visualizer/analysis/diff.py +133 -0
  30. sdr_visualizer-0.6.0/src/sdr_visualizer/analysis/formula_tree.py +121 -0
  31. sdr_visualizer-0.6.0/src/sdr_visualizer/analysis/references.py +74 -0
  32. sdr_visualizer-0.6.0/src/sdr_visualizer/analysis/segment_tree.py +198 -0
  33. sdr_visualizer-0.6.0/src/sdr_visualizer/analysis/trend.py +71 -0
  34. sdr_visualizer-0.6.0/src/sdr_visualizer/cli/__init__.py +0 -0
  35. sdr_visualizer-0.6.0/src/sdr_visualizer/cli/exit_codes.py +9 -0
  36. sdr_visualizer-0.6.0/src/sdr_visualizer/cli/main.py +322 -0
  37. sdr_visualizer-0.6.0/src/sdr_visualizer/core/__init__.py +0 -0
  38. sdr_visualizer-0.6.0/src/sdr_visualizer/core/exceptions.py +13 -0
  39. sdr_visualizer-0.6.0/src/sdr_visualizer/core/models.py +88 -0
  40. sdr_visualizer-0.6.0/src/sdr_visualizer/core/visualizer.py +45 -0
  41. sdr_visualizer-0.6.0/src/sdr_visualizer/input/__init__.py +0 -0
  42. sdr_visualizer-0.6.0/src/sdr_visualizer/input/detect.py +31 -0
  43. sdr_visualizer-0.6.0/src/sdr_visualizer/input/loader.py +158 -0
  44. sdr_visualizer-0.6.0/src/sdr_visualizer/input/series.py +116 -0
  45. sdr_visualizer-0.6.0/src/sdr_visualizer/input/shell_out.py +67 -0
  46. sdr_visualizer-0.6.0/src/sdr_visualizer/render/__init__.py +0 -0
  47. sdr_visualizer-0.6.0/src/sdr_visualizer/render/data_payload.py +181 -0
  48. sdr_visualizer-0.6.0/src/sdr_visualizer/render/renderer.py +111 -0
  49. sdr_visualizer-0.6.0/src/sdr_visualizer/render/static/d3.min.js +2 -0
  50. sdr_visualizer-0.6.0/src/sdr_visualizer/render/static/visualizer.css +836 -0
  51. sdr_visualizer-0.6.0/src/sdr_visualizer/render/static/visualizer.js +1275 -0
  52. sdr_visualizer-0.6.0/src/sdr_visualizer/render/templates/catalog.html.j2 +73 -0
  53. sdr_visualizer-0.6.0/src/sdr_visualizer/render/templates/changes.html.j2 +16 -0
  54. sdr_visualizer-0.6.0/src/sdr_visualizer/render/templates/graph.html.j2 +46 -0
  55. sdr_visualizer-0.6.0/src/sdr_visualizer/render/templates/index.html.j2 +99 -0
  56. sdr_visualizer-0.6.0/src/sdr_visualizer/render/templates/trend.html.j2 +13 -0
  57. sdr_visualizer-0.6.0/src/sdr_visualizer/render/trend_charts.py +68 -0
  58. sdr_visualizer-0.6.0/tests/__init__.py +0 -0
  59. sdr_visualizer-0.6.0/tests/conftest.py +52 -0
  60. sdr_visualizer-0.6.0/tests/fixtures/__init__.py +0 -0
  61. sdr_visualizer-0.6.0/tests/fixtures/aa_snapshot_clean.json +333 -0
  62. sdr_visualizer-0.6.0/tests/fixtures/aa_snapshot_messy.json +1194 -0
  63. sdr_visualizer-0.6.0/tests/fixtures/cja_snapshot_clean.json +1290 -0
  64. sdr_visualizer-0.6.0/tests/fixtures/cja_snapshot_hostile.json +78 -0
  65. sdr_visualizer-0.6.0/tests/fixtures/cja_snapshot_messy.json +10821 -0
  66. sdr_visualizer-0.6.0/tests/fixtures/cja_snapshot_minimal.json +71 -0
  67. sdr_visualizer-0.6.0/tests/test_adapter_fuzz.py +242 -0
  68. sdr_visualizer-0.6.0/tests/test_adapters_aa.py +245 -0
  69. sdr_visualizer-0.6.0/tests/test_adapters_cja.py +379 -0
  70. sdr_visualizer-0.6.0/tests/test_analysis_diff.py +228 -0
  71. sdr_visualizer-0.6.0/tests/test_analysis_formula_tree.py +127 -0
  72. sdr_visualizer-0.6.0/tests/test_analysis_references.py +112 -0
  73. sdr_visualizer-0.6.0/tests/test_analysis_segment_tree.py +164 -0
  74. sdr_visualizer-0.6.0/tests/test_analysis_trend.py +140 -0
  75. sdr_visualizer-0.6.0/tests/test_browser_functional.py +566 -0
  76. sdr_visualizer-0.6.0/tests/test_cli.py +497 -0
  77. sdr_visualizer-0.6.0/tests/test_cli_flags.py +74 -0
  78. sdr_visualizer-0.6.0/tests/test_corpus_check.py +75 -0
  79. sdr_visualizer-0.6.0/tests/test_data_payload.py +146 -0
  80. sdr_visualizer-0.6.0/tests/test_input_modes.py +224 -0
  81. sdr_visualizer-0.6.0/tests/test_input_series.py +168 -0
  82. sdr_visualizer-0.6.0/tests/test_minimal_fixture.py +82 -0
  83. sdr_visualizer-0.6.0/tests/test_mutate_fixture.py +42 -0
  84. sdr_visualizer-0.6.0/tests/test_perf.py +50 -0
  85. sdr_visualizer-0.6.0/tests/test_renderer.py +191 -0
  86. sdr_visualizer-0.6.0/tests/test_renderer_anatomy.py +88 -0
  87. sdr_visualizer-0.6.0/tests/test_renderer_graph.py +48 -0
  88. sdr_visualizer-0.6.0/tests/test_trend_charts.py +97 -0
@@ -0,0 +1,73 @@
1
+ # Local PM/planning artifacts (Superpowers brainstorms, plans, spikes)
2
+ .superpowers/
3
+
4
+ # Reference artifacts (kept in working directory, not tracked).
5
+ # These are the spec inputs and the grader's render artifacts copied here
6
+ # as references. The visualizer has its own templates under
7
+ # src/sdr_visualizer/render/.
8
+ /SPEC.md
9
+ /SPEC-VISUALIZER.md
10
+ /render.py
11
+ /report.css
12
+ /report.html.j2
13
+ /sample-report.html
14
+ /templated-report.html
15
+
16
+ # macOS
17
+ .DS_Store
18
+
19
+ # Python
20
+ __pycache__/
21
+ *.py[cod]
22
+ *$py.class
23
+ *.so
24
+ .Python
25
+ build/
26
+ dist/
27
+ *.egg-info/
28
+ .eggs/
29
+ *.egg
30
+ MANIFEST
31
+
32
+ # uv / virtualenvs
33
+ .venv/
34
+ venv/
35
+ env/
36
+
37
+ # Local tool state (Claude Code session state, Hypothesis example cache)
38
+ .claude/
39
+ .hypothesis/
40
+
41
+ # Testing
42
+ .pytest_cache/
43
+ .coverage
44
+ .coverage.*
45
+ htmlcov/
46
+ coverage.xml
47
+ .tox/
48
+
49
+ # Linters / type checkers
50
+ .ruff_cache/
51
+ .mypy_cache/
52
+ .pyre/
53
+
54
+ # IDE
55
+ .idea/
56
+ .vscode/
57
+ *.swp
58
+ *.swo
59
+
60
+ # Local-only generated artifacts
61
+ /visualize-*.html
62
+ /visualize-*.json
63
+
64
+ # Generated fixtures (materialized on demand by conftest.py)
65
+ /tests/fixtures/cja_snapshot_large.json
66
+ /tests/fixtures/aa_snapshot_large.json
67
+ /tests/fixtures/cja_snapshot_xl.json
68
+ /tests/fixtures/cja_snapshot_small.json
69
+ /tests/fixtures/cja_snapshot_medium.json
70
+
71
+ # Environment
72
+ .env
73
+ .env.local
@@ -0,0 +1,301 @@
1
+ # Changelog
2
+
3
+ All notable changes to `sdr-visualizer` will be documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
+
5
+ ## [0.6.0] - 2026-07-17
6
+
7
+ ### Added
8
+
9
+ - Property-based fuzz suite (`tests/test_adapter_fuzz.py`, ported from
10
+ sdr-grader and extended): random and mutated-fixture inputs must produce
11
+ a valid report or `InvalidSnapshotError` — never a bare crash — through
12
+ the adapters AND the render path, including the NaN/Infinity payload
13
+ class.
14
+ - Browser functional tests now run on webkit as well as Chromium in CI;
15
+ performance budgets remain Chromium-only by design.
16
+ - `scripts/corpus_check.py`: sweep a private directory tree of real
17
+ snapshots through the full build, asserting adapter acceptance, payload
18
+ serializability, embedded-payload parseability, and (optionally) the
19
+ §6 size budget per tier. A clean corpus sweep becomes the 1.0.0 gate;
20
+ swept clean ahead of this release over the local real corpus
21
+ (108 snapshots, budget checks included).
22
+ - Contributor hygiene: `CONTRIBUTING.md`, PR and issue templates, and
23
+ `SECURITY.md` with private reporting via GitHub security advisories.
24
+ - The example reports can be published via GitHub Pages (`pages.yml`;
25
+ deploys are manual until Pages is enabled for the repo). The README's
26
+ catalog screenshot and live-example links are held back until the site
27
+ is live — the README is also the PyPI page, where they cannot resolve
28
+ yet; `docs/screenshot-catalog.png` ships in the repo meanwhile.
29
+ - Dependabot keeps the SHA-pinned GitHub Actions and the Python
30
+ dependencies current (weekly, grouped into one PR per ecosystem).
31
+
32
+ ### Changed
33
+
34
+ - Releases now publish to PyPI via trusted publishing (OIDC, `pypi`
35
+ environment) as a hard step — a publish failure fails the release.
36
+ Release assets no longer include the stray `default.gitignore` file.
37
+ - README install instructions point at PyPI again (0.6.0 is the first
38
+ published release); installing from the repo remains the development
39
+ path.
40
+ - CI pins `astral-sh/setup-uv` by commit SHA at v8.3.2 (previously the
41
+ mutable `v3` tag) and sets `prune-cache: false`, so cached pre-built
42
+ wheels are no longer stripped and re-downloaded from PyPI on every
43
+ run. The release build job now uses the cache too.
44
+
45
+ ### Fixed
46
+
47
+ - The AA and CJA adapters no longer crash with a bare `TypeError`/`ValueError`
48
+ on a malformed optional field (found by the new fuzz suite). A truthy
49
+ non-list `tags` or `*_references` value degrades to an empty list; a
50
+ present-but-unconvertible `nesting_depth` or `complexity_score` now raises
51
+ `InvalidSnapshotError` (so a single snapshot exits 3 and the trend loader
52
+ skips such a snapshot) instead of a bare exception. These files are
53
+ vendored from sdr-grader, which already carried equivalent type guards —
54
+ the visualizer had drifted behind, and nothing is owed to the sibling repo.
55
+ - The sdist is now built from an explicit file allowlist
56
+ (`[tool.hatch.build.targets.sdist]`). Hatchling's default includes every
57
+ non-gitignored file in the project directory — tracked or not — which
58
+ swept local tool state (a Claude Code lock file, the Hypothesis example
59
+ cache) and repo-meta content (the example HTML pages, the README
60
+ screenshot, `uv.lock`, `CLAUDE.md`) into the tarball. The wheel was
61
+ always restricted to `src/sdr_visualizer` and is unaffected, and no sdist
62
+ was ever published (PyPI publishing starts with 0.6.0). `.claude/` and
63
+ `.hypothesis/` are gitignored as a second layer of defense.
64
+
65
+ ## [0.5.0] - 2026-07-12
66
+
67
+ ### Added
68
+
69
+ - **Trend mode.** `--trend` on a snapshot directory adds a Trend view:
70
+ server-rendered sparkline charts of descriptive aggregates (total and
71
+ per-type component counts, orphans, components without a description,
72
+ reference edge count) across the directory's snapshots, plus a change log
73
+ with one expandable row per adjacent snapshot pair (added / removed /
74
+ modified component ids, computed with the 0.4.0 diff engine). The window
75
+ honors `--at` as its end and is capped at the 60 most recent parseable
76
+ snapshots with a build warning. Unparseable snapshots and snapshots with an
77
+ unconvertible scalar field are skipped with warnings. A directory that mixes
78
+ platforms exits 3 unless `--platform cja|aa` selects one, and a directory
79
+ that mixes data views / report suites exits 3, so unrelated inventories are
80
+ never diffed (mirroring `--compare-to`, which refuses both). Fewer than 2
81
+ usable snapshots exits 3, as does combining `--trend` with `--compare-to`,
82
+ `--dataview`, `--rsid`, a file path, or stdin. (SPEC §13, trend mode)
83
+ - **`--allow-instance-mismatch`.** Opt-in flag that lets `--compare-to` and
84
+ `--trend` span different data views / report suites on purpose (for example
85
+ staging versus prod drift); the run proceeds with a warning instead of
86
+ exiting 3. Platform mismatches are always rejected.
87
+
88
+ ### Changed
89
+
90
+ - **`--compare-to` now refuses an instance mismatch.** Comparing snapshots
91
+ from different data views / report suites exits 3 instead of warning and
92
+ proceeding (0.4.0 behavior), matching `--trend`; both views require a single
93
+ implementation so the diff never spans unrelated inventories. Pass
94
+ `--allow-instance-mismatch` to restore the cross-instance comparison on
95
+ demand.
96
+
97
+ ### Fixed
98
+
99
+ - **Cross-command flag consistency.** `--at` now resolves a `--compare-to`
100
+ baseline directory the same way it resolves the primary directory (it was
101
+ silently ignored for the baseline, which always used the latest snapshot).
102
+ A directory mixing timestamped and un-timestamped snapshots now warns about
103
+ the dropped un-timestamped files in single-snapshot and `--compare-to`
104
+ selection, matching `--trend`. `--platform` combined with `--dataview` /
105
+ `--rsid` is ignored with a warning instead of forcing a mismatched adapter.
106
+
107
+ ## [0.4.0] - 2026-07-11
108
+
109
+ ### Added
110
+
111
+ - **Comparative view.** `--compare-to BASELINE` (a snapshot file, or a
112
+ directory that resolves to its latest snapshot) adds a Changes view to the
113
+ report: components added, removed, and modified against the baseline, with
114
+ field-level before/after detail. The diff is computed at build time and
115
+ embedded as the payload's `changes` section — the baseline snapshot itself
116
+ is never embedded, so the report grows only with the size of the diff.
117
+ A platform mismatch between the two snapshots exits 3; differing instance
118
+ ids warn but proceed. (SPEC §13, comparative view)
119
+
120
+ ## [0.3.0] - 2026-07-11
121
+
122
+ ### Changed
123
+
124
+ - **Graph view warm-up no longer freezes on huge graphs.** The synchronous
125
+ warm-up that pre-settles the force simulation now runs until near-settled
126
+ or a 150 ms budget elapses, whichever comes first (it ran a fixed 60–120
127
+ ticks before — ~0.8 s blocked at 2,000 nodes, ~2.2 s at 5,000). Graphs
128
+ that can't settle in budget finish asynchronously, one tick per frame.
129
+ The simulation also uses a coarser Barnes-Hut theta and faster alpha
130
+ decay (~30% cheaper ticks) above 1,000 nodes — §6's interactive ceiling —
131
+ or above a lower `--max-graph-nodes` opt-in threshold, whichever is
132
+ smaller.
133
+ - **Graph hover/filter repaints coalesced to one pass per frame.** Filter
134
+ state is recomputed only when a filter input changes; hover and filter
135
+ paints apply all node/link classes in a single pass per selection and are
136
+ batched through `requestAnimationFrame`. Sweeping the pointer across a
137
+ 5,000-node graph previously cost up to ~3.6 ms per mouseover/mouseout
138
+ event in full selection walks. The graph search box is now debounced
139
+ (120 ms), matching the catalog search. The hover/filter contract is now
140
+ explicit: hover fading wins while active, search-match highlights persist
141
+ through hover (now honored visually too — the fade previously dimmed the
142
+ highlight ring), and any filter or search change cancels an active hover
143
+ and repaints on the next frame. (Also fixes a pre-existing bug where a
144
+ hovered node's edge highlights could linger after mouseout.)
145
+
146
+ ### Added
147
+
148
+ - `THIRD_PARTY_LICENSES` with the D3 v7.9.0 ISC license notice — D3 is
149
+ redistributed in the repo, in built packages (wheel `dist-info/licenses/`,
150
+ sdist), and inlined into every generated report. `pyproject.toml` now uses
151
+ the SPDX `license` string + `license-files` so both files ship in
152
+ distributions.
153
+ - **Shareable URL state.** Catalog search, type/description/references/modified
154
+ filters, sort, the active view, and the open detail panel are reflected in
155
+ `location.hash` — copy the URL to share a filtered view ("every undocumented
156
+ metric"). Restored on load. (SPEC §14 Q1)
157
+ - **Radial layout for small graphs.** Implementations with fewer than 20
158
+ components skip the force simulation and place nodes evenly on a circle —
159
+ force-directed layouts look chaotic at that size. Drag and reset still work.
160
+ (SPEC §14 Q2)
161
+ - CI browser gate now also asserts entering the graph view blocks the main
162
+ thread < 700 ms (`scripts/perf_browser_check.py`) — script time plus a
163
+ forced style/layout flush of the inserted SVG — timing the worst-case
164
+ "Render anyway" path on both large fixtures (and failing if that path
165
+ stops being exercised); plus browser-level functional tests for hover
166
+ neighbor-highlighting, graph search fade/highlight, and filter-cancels-
167
+ hover behavior.
168
+ - Perf gate now enforces the SPEC §6 100- and 500-component tiers (build
169
+ time + HTML size) via generated small fixtures, and the browser gate
170
+ covers the AA path and asserts per-tier budgets (1,000-component budgets
171
+ on the large fixtures, 2,000-component on XL).
172
+
173
+ ### Fixed
174
+
175
+ - Canonical Adobe segment roots (`{"func": "segment", "container": {...}}`)
176
+ parse into a full anatomy tree instead of collapsing to one empty
177
+ segment reference.
178
+ - Snapshots containing `NaN`/`Infinity` exit 3 with a clear message instead
179
+ of emitting a report whose payload the browser cannot parse (exit 0).
180
+ - Usage errors exit 3, not argparse's default 2 (SPEC §7 forbids 2), and
181
+ unwritable `--output`/`--json` paths exit 1 with a clean message instead
182
+ of a traceback.
183
+ - AA segment `nesting_depth` counts container nesting; it previously
184
+ reported raw JSON depth (a single-container segment claimed depth 4).
185
+ - Explicit JSON `null` reference keys in CJA snapshots parse as empty
186
+ instead of crashing.
187
+ - `--at` accepts full ISO-8601 (offsets, fractional seconds) and warns when
188
+ passed with non-directory input modes, where it never applied.
189
+ - Date cells display the timestamp's date prefix — Safari showed raw
190
+ strings and Chrome could drift a day against the modified filter.
191
+ - A shared URL with every type filter unchecked restores as unchecked.
192
+ - AA calc-metric formula summaries render nested formulas readably instead
193
+ of leaking Python dict syntax; classifications without a name or id no
194
+ longer add blank tag chips.
195
+ - Duplicate component ids in a snapshot print a build warning (anatomy
196
+ trees for duplicated ids are last-writer-wins).
197
+ - Shell-out output decodes as UTF-8 regardless of host locale.
198
+
199
+ ### Security
200
+
201
+ - The embedded JSON payload now escapes `<` as the JSON unicode escape `\u003c` (transparent to `JSON.parse`). Previously a snapshot
202
+ field containing `</script>` (e.g. a hostile component description) could
203
+ terminate the data block and inject live markup into the generated HTML.
204
+ - Jinja autoescaping now applies to all templates (`autoescape=True`).
205
+ `select_autoescape(["html"])` never matched `index.html.j2` (final
206
+ extension `.j2`, not `.html`), so template-interpolated values — the
207
+ document title and instance name — rendered unescaped: a hostile Data
208
+ View name could inject live markup into the page. Same class of stored
209
+ XSS as the payload escape above; both shipped in the same fix pass.
210
+
211
+ ## [0.2.0] - 2026-06-10
212
+
213
+ Performance and scaling release. The embedded payload contract changed (see
214
+ Removed) — per the stability policy this bumps the leftmost non-zero version.
215
+
216
+ ### Added
217
+
218
+ - `modified_ts` (epoch ms) on every catalog entry — the client sorts and
219
+ date-windows without constructing `Date` objects in hot paths.
220
+ - Browser-side performance gate (`scripts/perf_browser_check.py`, Playwright):
221
+ initial render + filter latency budgets now enforced in CI alongside the
222
+ existing build-time/size gate.
223
+ - 2,000-component budget tier gated in CI via a generated XL fixture
224
+ (`scripts/generate_large_fixture.py --scale 1.67`).
225
+ - Catalog row cap: above 1,000 matching rows the table truncates with a
226
+ "Showing 1,000 of N · Show all" escape hatch.
227
+ - Graph label culling (graphs over 200 nodes label only the top-60 in-degree nodes by default; all labels
228
+ past 1.4× zoom or on hover) and warm-started force simulation (near-settled
229
+ first paint).
230
+
231
+ ### Changed
232
+
233
+ - Client builds the search index and sort keys in one pass at load; row HTML
234
+ is cached per entry; the master list stays pre-sorted; search input is
235
+ debounced (120 ms). Net: filter latency stays flat into the thousands of
236
+ components.
237
+ - Graph hover/filter passes use precomputed neighbor counts.
238
+
239
+ ### Removed (breaking, embedded payload + `--json` shape)
240
+
241
+ - `catalog_index` — the client builds its own search blob at load.
242
+ - `graph.nodes`, `graph.in_degree`, `graph.out_degree` — derivable from
243
+ catalog entries; only `graph.edges` ships.
244
+ - `platform_specific` on component entries — never consumed by the client;
245
+ consult the original snapshot for platform extras.
246
+ - Null/empty fields on entries (sparse encoding) — consumers must treat a
247
+ missing key as null/empty.
248
+
249
+ Net payload reduction: ~40% at the 1,200-component tier.
250
+
251
+ ## [0.1.0] - 2026-05-09
252
+
253
+ First releasable cut. Feature-complete per [`SPEC-VISUALIZER.md`](SPEC-VISUALIZER.md) §10 phases 0–10. Surface (CLI flags, `--json` payload shape, exit codes, embedded payload schema) is documented but not yet hardened against real-world implementations — expect changes between 0.x releases as feedback comes in.
254
+
255
+ ### Added
256
+
257
+ - **Catalog view (primary).** Searchable, filterable, sortable table of every component (metric / dimension / derived field / segment / calculated metric). Click a row to slide out a detail panel with description, properties, references, and anatomy.
258
+ - **Reference graph view.** D3 force-directed layout with per-type color coding, in-degree-proportional node sizing, hover-to-highlight neighbors, drag-to-pin, pan + zoom, and graceful degradation above the 1,000-node threshold (configurable via `--max-graph-nodes`).
259
+ - **Segment anatomy.** Reached from a segment's detail panel. Renders the segment definition tree as nested containers with subtle alpha-stacked shading per nesting depth, color-coded AND/OR/NOT operators, and clickable inline references to other segments.
260
+ - **Calculated-metric anatomy.** Reached from a calc metric's detail panel. Renders the formula as a tree of operations and operands; metric refs are clickable.
261
+ - **CJA + AA adapters.** Vendored from `sdr-grader` v1.0 per SPEC §15. Handle missing-description normalization, segment depth + container-context extraction, calc-metric formula parsing across both shapes (CJA `col1`/`col2`, AA `args`), classifications-as-tags for AA.
262
+ - **Four input modes.** File path, snapshot directory (with `--at TIMESTAMP`), shell-out to `cja_auto_sdr` / `aa_auto_sdr`, stdin.
263
+ - **CLI flags.** `--platform`, `--output`, `--json`, `--title`, `--exclude-orphans`, `--max-graph-nodes`, `--at`, `--quiet`, `--version`. Exit codes 0 / 1 / 3 per SPEC §7.
264
+ - **Single-file output.** All CSS, JS (vanilla + D3 v7), and JSON payload inlined. No fetches, no CDNs, no external resources.
265
+ - **Performance gate.** `scripts/perf_check.py` enforces SPEC §6 budgets (build time + HTML size) for both CJA and AA at the 1,000-component class. Wired into CI.
266
+ - **Documentation.** [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md), [`docs/PERFORMANCE.md`](docs/PERFORMANCE.md), [`docs/ADAPTER_GUIDE.md`](docs/ADAPTER_GUIDE.md), [`docs/EMBEDDED_DATA_FORMAT.md`](docs/EMBEDDED_DATA_FORMAT.md).
267
+
268
+ ### Stability
269
+
270
+ The following surfaces are intended to be SemVer-stable from 0.1 onwards (breaking changes will bump the leftmost non-zero version):
271
+
272
+ - CLI flag names and behavior
273
+ - Exit codes
274
+ - The embedded JSON payload's documented top-level keys (per `docs/EMBEDDED_DATA_FORMAT.md`)
275
+ - The `--json` output shape
276
+
277
+ The following are explicitly internal and may change without notice:
278
+
279
+ - Template HTML structure / class names / IDs
280
+ - CSS selectors
281
+ - Module layout under `sdr_visualizer.*` (use the CLI or import from `sdr_visualizer.core.visualizer` only)
282
+
283
+ ### Known limitations
284
+
285
+ - Not yet validated against real customer `cja_auto_sdr` / `aa_auto_sdr` output beyond the vendored grader fixtures. *(Since resolved: validated against real cja_auto_sdr / aa_auto_sdr output.)*
286
+ - No browser-side performance gate yet (Python build time + HTML size are gated; client-side render and filter latency aren't). *(Resolved in 0.2.0 by scripts/perf_browser_check.py.)*
287
+ - The PyPI publish step in `release.yml` is `continue-on-error: true` until trusted-publisher is configured at pypi.org. *(Resolved in 0.6.0: publishing moved to a hard, gated publish job — a failure fails the release.)*
288
+
289
+ ### Deferred to later releases (per SPEC §13)
290
+
291
+ - Comparative view, two snapshots side-by-side (v0.2) *(Shipped in 0.4.0.)*
292
+ - Trend mode against a directory of snapshots (v0.3) *(Shipping in 0.5.0.)*
293
+ - Workspace project visualization (v0.4)
294
+ - Schema map view (v0.5)
295
+
296
+ [0.6.0]: https://github.com/brian-a-au/sdr-visualizer/releases/tag/v0.6.0
297
+ [0.5.0]: https://github.com/brian-a-au/sdr-visualizer/releases/tag/v0.5.0
298
+ [0.4.0]: https://github.com/brian-a-au/sdr-visualizer/releases/tag/v0.4.0
299
+ [0.3.0]: https://github.com/brian-a-au/sdr-visualizer/releases/tag/v0.3.0
300
+ [0.2.0]: https://github.com/brian-a-au/sdr-visualizer/releases/tag/v0.2.0
301
+ [0.1.0]: https://github.com/brian-a-au/sdr-visualizer/releases/tag/v0.1.0
@@ -0,0 +1,59 @@
1
+ # Contributing to sdr-visualizer
2
+
3
+ Thanks for your interest. This project has a deliberately tight scope and a
4
+ few non-negotiable architectural rules; reading this first will save you a
5
+ review round-trip.
6
+
7
+ ## The rules of the road
8
+
9
+ These are settled decisions (see the spec history), not open questions:
10
+
11
+ - **Static output, dynamic interaction.** The tool emits one self-contained
12
+ HTML file: embedded JSON, embedded CSS, embedded JS. No fetches, no CDNs,
13
+ no server, no build step for the consumer.
14
+ - **Server-side build, client-side render.** Analysis work happens in
15
+ Python, where seconds are fine. The client reads, filters, and renders
16
+ against a millisecond budget.
17
+ - **Vanilla JS.** D3 for the reference graph only. No frameworks, no new
18
+ JS dependencies.
19
+ - **Descriptive, not evaluative.** The visualizer reports what an
20
+ implementation contains and how it changed — never whether that is good
21
+ or bad. Grading belongs in the sibling project,
22
+ [sdr-grader](https://github.com/brian-a-au/sdr-grader).
23
+ - **Performance budgets are CI-gated.** `docs/PERFORMANCE.md` documents
24
+ them. A change that blows a budget fails CI regardless of its merits.
25
+ - **Exit codes are 0 / 1 / 3.** 0 success, 1 runtime error, 3 invalid
26
+ input. Code 2 is never used.
27
+
28
+ ## The vendoring relationship
29
+
30
+ `src/sdr_visualizer/adapters/*` and `src/sdr_visualizer/input/{loader,detect,shell_out}.py`
31
+ are vendored from sdr-grader, and the `core/models.py` shapes are shared.
32
+ Any behavioral change to those files must be mirrored to the sibling repo in
33
+ the same cycle — a PR that changes them should say how the parity obligation
34
+ is being met. `input/series.py` is visualizer-only and exempt.
35
+
36
+ ## Developing
37
+
38
+ ```bash
39
+ uv sync --dev --group browser # environment
40
+ uv run playwright install chromium webkit
41
+ uv run pytest # tests (includes browser tests)
42
+ uv run ruff check # lint
43
+ uv run ruff format # format (the repo is format-clean)
44
+ uv run python scripts/perf_check.py # build/size budgets
45
+ uv run python scripts/perf_browser_check.py # browser budgets
46
+ ```
47
+
48
+ Every PR needs: tests for behavior changes, a green suite, clean
49
+ `ruff check` and `ruff format --check`, and green perf gates when the
50
+ change could plausibly affect them.
51
+
52
+ ## Releases (maintainer notes)
53
+
54
+ The release commit bumps `pyproject.toml`, `src/sdr_visualizer/__init__.py`,
55
+ and `uv.lock` together, dates the CHANGELOG's Unreleased section, and adds
56
+ the version's link definition at the bottom of the file. Tag the release
57
+ commit itself — never a commit whose message contains `[skip ci]` (the
58
+ examples auto-commits), because GitHub skips all workflows for such a tag.
59
+ Do not commit locally generated `examples/*.html`; CI regenerates them.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brian Au
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.4
2
+ Name: sdr-visualizer
3
+ Version: 0.6.0
4
+ Summary: Static-output visual catalog generator for Adobe Customer Journey Analytics and Adobe Analytics implementations.
5
+ Project-URL: Homepage, https://github.com/brian-a-au/sdr-visualizer
6
+ Project-URL: Issues, https://github.com/brian-a-au/sdr-visualizer/issues
7
+ Author: Brian Au
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ License-File: THIRD_PARTY_LICENSES
11
+ Requires-Python: >=3.11
12
+ Requires-Dist: jinja2>=3.1
13
+ Requires-Dist: pydantic>=2.6
14
+ Requires-Dist: pyyaml>=6.0
15
+ Description-Content-Type: text/markdown
16
+
17
+ # sdr-visualizer
18
+
19
+ [![Tests](https://github.com/brian-a-au/sdr-visualizer/actions/workflows/test.yml/badge.svg)](https://github.com/brian-a-au/sdr-visualizer/actions/workflows/test.yml)
20
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/)
21
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
22
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
23
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
24
+
25
+ Static-output visual catalog generator for Adobe Customer Journey Analytics (CJA) and Adobe Analytics (AA) implementations. Consumes JSON snapshots from [`cja_auto_sdr`](https://github.com/brian-a-au/cja_auto_sdr) and [`aa_auto_sdr`](https://github.com/brian-a-au/aa_auto_sdr) and produces a single self-contained HTML file with:
26
+
27
+ - A searchable, filterable component catalog (the primary view)
28
+ - An interactive force-directed reference graph
29
+ - Per-segment anatomy diagrams that make deeply-nested segments legible
30
+ - Per-calculated-metric formula trees with click-through to referenced metrics
31
+
32
+ The output is one HTML file: no server, no build step on the consumer side, no CDN dependencies. Everything is built into that one file. The component data is stored as JSON, the styling as CSS, and the interactive code as JavaScript, all inside it. You open it by double-clicking it in any modern web browser, and it works without an internet connection. There are no network requests, so no data is sent anywhere, and you can open it safely inside a locked-down corporate environment. You can move it, rename it, or copy it anywhere, and it still opens the same way. Drop it on a wiki, email it to a stakeholder, screenshot it into a deck.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ uv tool install sdr-visualizer
38
+ ```
39
+
40
+ Or with pip:
41
+
42
+ ```bash
43
+ pip install sdr-visualizer
44
+ ```
45
+
46
+ (Requires 0.6.0 or later on PyPI.) For development, run from a clone:
47
+
48
+ ```bash
49
+ git clone https://github.com/brian-a-au/sdr-visualizer
50
+ cd sdr-visualizer
51
+ uv sync
52
+ uv run sdr-visualizer --help
53
+ ```
54
+
55
+ ## Quickstart
56
+
57
+ ```bash
58
+ # Mode 1: from a snapshot file
59
+ sdr-visualizer path/to/snapshot.json
60
+
61
+ # Mode 2: from a directory of snapshots (uses the most recent)
62
+ sdr-visualizer path/to/snapshots/
63
+
64
+ # Mode 3: shell out to the upstream tool
65
+ sdr-visualizer --dataview dv_prod_web # CJA
66
+ sdr-visualizer --rsid prod_us # AA
67
+
68
+ # Mode 4: stdin
69
+ cja_auto_sdr dv_prod_web --format json --output - | sdr-visualizer -
70
+
71
+ # Compare against an earlier snapshot: adds a Changes view to the report
72
+ sdr-visualizer snapshot_new.json --compare-to snapshot_old.json
73
+
74
+ # Chart evolution across a directory of snapshots: adds a Trend view
75
+ sdr-visualizer ./snapshots/ --trend
76
+ ```
77
+
78
+ The output lands at `./visualize-{instance_id}-{timestamp}.html` by default. Open it in a browser — that's the whole experience.
79
+
80
+ ## Useful flags
81
+
82
+ | Flag | What it does |
83
+ |---|---|
84
+ | `--output PATH` | Write HTML somewhere specific. |
85
+ | `--json PATH` | Also emit the embedded payload as a separate JSON file (useful for downstream tooling). |
86
+ | `--title TEXT` | Override the document title. |
87
+ | `--exclude-orphans` | Default the catalog's references filter to "Referenced" — hides components nothing depends on. |
88
+ | `--max-graph-nodes N` | Override the 1,000-node graph-rendering threshold. |
89
+ | `--platform cja\|aa` | Override platform auto-detection. |
90
+ | `--at TIMESTAMP` | When path is a directory, pick the snapshot closest to (and not after) this timestamp. |
91
+ | `--quiet` | Suppress informational stderr output. |
92
+
93
+ ## What's in the output
94
+
95
+ Open the generated HTML and you'll see four views, accessible from the top-level navigation:
96
+
97
+ 1. **Catalog** — a searchable, filterable, sortable table of every component. Click a row to slide out a detail panel with description, properties, references, and anatomy.
98
+ 2. **Reference graph** — a force-directed view of every component and the edges between them; small implementations (under 20 components) use a static radial layout instead. Hover dims unrelated nodes; click opens the same detail panel; drag pins; pan/zoom.
99
+ 3. **Segment anatomy** (contextual) — opens from a segment's detail panel. Renders the segment's definition tree as nested containers with subtle alpha-stacked shading per nesting level, color-coded AND/OR/NOT chips, and clickable inline references to other segments.
100
+ 4. **Calculated metric anatomy** (contextual) — opens from a calc metric's detail panel. Renders the formula as a tree of operations and operands; metric refs are clickable.
101
+
102
+ With `--compare-to`, a fifth **Changes** view appears, listing components
103
+ added, removed, and modified relative to a baseline snapshot, with
104
+ field-level before/after detail.
105
+
106
+ With `--trend` on a snapshot directory, a **Trend** view appears: sparkline
107
+ charts of descriptive aggregates (component counts, orphans, undocumented
108
+ components, reference edges) across the directory's snapshots, plus a
109
+ per-interval change log. The window is capped at the 60 most recent
110
+ snapshots.
111
+
112
+ A trend directory must hold snapshots of a single implementation. If it mixes
113
+ CJA and AA snapshots, pass `--platform cja|aa` to select one (or point at a
114
+ single-platform directory); without it the run stops rather than guess. If it
115
+ mixes data views or report suites, the run stops as well. This mirrors
116
+ `--compare-to`, which refuses both a platform and an instance mismatch, so
117
+ neither view ever diffs unrelated inventories. To compare or chart across
118
+ different data views or report suites on purpose (for example staging versus
119
+ prod drift), pass `--allow-instance-mismatch`; the run then proceeds with a
120
+ warning. Platform mismatches are always rejected. The report shown alongside
121
+ the trend is the newest usable snapshot in the directory.
122
+
123
+ - **Shareable links** — the catalog's filters, sort, view, and open detail panel are encoded in the URL hash; copy the address bar to share a filtered view.
124
+
125
+ ## Performance budget
126
+
127
+ The output is CI-gated against the budgets in [`docs/PERFORMANCE.md`](docs/PERFORMANCE.md).
128
+ Build time and HTML size are enforced at every §6 tier (100 / 500 / 1,000 / 2,000
129
+ components). Browser-measured budgets are enforced at the 1,000-component tier
130
+ (initial render < 1s, filter/search < 150ms) and the 2,000-component tier
131
+ (< 2s, < 300ms), plus a 700ms cap on the graph view's main-thread block.
132
+
133
+ ## Develop
134
+
135
+ Requires Python 3.11+ and [uv](https://github.com/astral-sh/uv).
136
+
137
+ ```bash
138
+ uv sync # Set up environment
139
+ uv run pytest # Run tests (auto-generates the large fixture on first run)
140
+ uv run ruff check # Lint
141
+ uv run ruff format # Auto-format
142
+
143
+ uv run python scripts/generate_examples.py # Regenerate examples/
144
+ uv run python scripts/perf_check.py # Run the perf gate
145
+ ```
146
+
147
+ ## See also
148
+
149
+ - [`sdr-grader`](https://github.com/brian-a-au/sdr-grader) — deterministic, rule-based linter for the same input format.
150
+ - [`cja_auto_sdr`](https://github.com/brian-a-au/cja_auto_sdr) — generates CJA snapshots.
151
+ - [`aa_auto_sdr`](https://github.com/brian-a-au/aa_auto_sdr) — generates AA snapshots.
152
+
153
+ ## Documentation
154
+
155
+ - [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — module layout, one-way data flow, design principles.
156
+ - [`docs/ADAPTER_GUIDE.md`](docs/ADAPTER_GUIDE.md) — how the CJA and AA adapters work, and how to add a new platform.
157
+ - [`docs/PERFORMANCE.md`](docs/PERFORMANCE.md) — performance budgets and how they're enforced.
158
+ - [`docs/EMBEDDED_DATA_FORMAT.md`](docs/EMBEDDED_DATA_FORMAT.md) — the JSON payload format embedded in the HTML output.
159
+
160
+ ## License
161
+
162
+ MIT — see [`LICENSE`](LICENSE). The output bundles [D3](https://d3js.org) v7,
163
+ vendored under the ISC license; see
164
+ [`THIRD_PARTY_LICENSES`](THIRD_PARTY_LICENSES) for the full notice.