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.
- sdr_visualizer-0.6.0/.gitignore +73 -0
- sdr_visualizer-0.6.0/CHANGELOG.md +301 -0
- sdr_visualizer-0.6.0/CONTRIBUTING.md +59 -0
- sdr_visualizer-0.6.0/LICENSE +21 -0
- sdr_visualizer-0.6.0/PKG-INFO +164 -0
- sdr_visualizer-0.6.0/README.md +148 -0
- sdr_visualizer-0.6.0/SECURITY.md +24 -0
- sdr_visualizer-0.6.0/THIRD_PARTY_LICENSES +27 -0
- sdr_visualizer-0.6.0/docs/ADAPTER_GUIDE.md +105 -0
- sdr_visualizer-0.6.0/docs/ARCHITECTURE.md +84 -0
- sdr_visualizer-0.6.0/docs/EMBEDDED_DATA_FORMAT.md +177 -0
- sdr_visualizer-0.6.0/docs/PERFORMANCE.md +45 -0
- sdr_visualizer-0.6.0/pyproject.toml +68 -0
- sdr_visualizer-0.6.0/scripts/capture_screenshot.py +60 -0
- sdr_visualizer-0.6.0/scripts/corpus_check.py +134 -0
- sdr_visualizer-0.6.0/scripts/generate_aa_large_fixture.py +154 -0
- sdr_visualizer-0.6.0/scripts/generate_examples.py +42 -0
- sdr_visualizer-0.6.0/scripts/generate_large_fixture.py +196 -0
- sdr_visualizer-0.6.0/scripts/mutate_fixture.py +97 -0
- sdr_visualizer-0.6.0/scripts/perf_browser_check.py +259 -0
- sdr_visualizer-0.6.0/scripts/perf_check.py +242 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/__init__.py +3 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/__main__.py +6 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/adapters/__init__.py +0 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/adapters/aa.py +370 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/adapters/base.py +1 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/adapters/cja.py +496 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/analysis/__init__.py +0 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/analysis/diff.py +133 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/analysis/formula_tree.py +121 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/analysis/references.py +74 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/analysis/segment_tree.py +198 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/analysis/trend.py +71 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/cli/__init__.py +0 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/cli/exit_codes.py +9 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/cli/main.py +322 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/core/__init__.py +0 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/core/exceptions.py +13 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/core/models.py +88 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/core/visualizer.py +45 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/input/__init__.py +0 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/input/detect.py +31 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/input/loader.py +158 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/input/series.py +116 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/input/shell_out.py +67 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/render/__init__.py +0 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/render/data_payload.py +181 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/render/renderer.py +111 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/render/static/d3.min.js +2 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/render/static/visualizer.css +836 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/render/static/visualizer.js +1275 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/render/templates/catalog.html.j2 +73 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/render/templates/changes.html.j2 +16 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/render/templates/graph.html.j2 +46 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/render/templates/index.html.j2 +99 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/render/templates/trend.html.j2 +13 -0
- sdr_visualizer-0.6.0/src/sdr_visualizer/render/trend_charts.py +68 -0
- sdr_visualizer-0.6.0/tests/__init__.py +0 -0
- sdr_visualizer-0.6.0/tests/conftest.py +52 -0
- sdr_visualizer-0.6.0/tests/fixtures/__init__.py +0 -0
- sdr_visualizer-0.6.0/tests/fixtures/aa_snapshot_clean.json +333 -0
- sdr_visualizer-0.6.0/tests/fixtures/aa_snapshot_messy.json +1194 -0
- sdr_visualizer-0.6.0/tests/fixtures/cja_snapshot_clean.json +1290 -0
- sdr_visualizer-0.6.0/tests/fixtures/cja_snapshot_hostile.json +78 -0
- sdr_visualizer-0.6.0/tests/fixtures/cja_snapshot_messy.json +10821 -0
- sdr_visualizer-0.6.0/tests/fixtures/cja_snapshot_minimal.json +71 -0
- sdr_visualizer-0.6.0/tests/test_adapter_fuzz.py +242 -0
- sdr_visualizer-0.6.0/tests/test_adapters_aa.py +245 -0
- sdr_visualizer-0.6.0/tests/test_adapters_cja.py +379 -0
- sdr_visualizer-0.6.0/tests/test_analysis_diff.py +228 -0
- sdr_visualizer-0.6.0/tests/test_analysis_formula_tree.py +127 -0
- sdr_visualizer-0.6.0/tests/test_analysis_references.py +112 -0
- sdr_visualizer-0.6.0/tests/test_analysis_segment_tree.py +164 -0
- sdr_visualizer-0.6.0/tests/test_analysis_trend.py +140 -0
- sdr_visualizer-0.6.0/tests/test_browser_functional.py +566 -0
- sdr_visualizer-0.6.0/tests/test_cli.py +497 -0
- sdr_visualizer-0.6.0/tests/test_cli_flags.py +74 -0
- sdr_visualizer-0.6.0/tests/test_corpus_check.py +75 -0
- sdr_visualizer-0.6.0/tests/test_data_payload.py +146 -0
- sdr_visualizer-0.6.0/tests/test_input_modes.py +224 -0
- sdr_visualizer-0.6.0/tests/test_input_series.py +168 -0
- sdr_visualizer-0.6.0/tests/test_minimal_fixture.py +82 -0
- sdr_visualizer-0.6.0/tests/test_mutate_fixture.py +42 -0
- sdr_visualizer-0.6.0/tests/test_perf.py +50 -0
- sdr_visualizer-0.6.0/tests/test_renderer.py +191 -0
- sdr_visualizer-0.6.0/tests/test_renderer_anatomy.py +88 -0
- sdr_visualizer-0.6.0/tests/test_renderer_graph.py +48 -0
- 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
|
+
[](https://github.com/brian-a-au/sdr-visualizer/actions/workflows/test.yml)
|
|
20
|
+
[](https://www.python.org/downloads/)
|
|
21
|
+
[](https://github.com/astral-sh/ruff)
|
|
22
|
+
[](https://github.com/astral-sh/uv)
|
|
23
|
+
[](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.
|