reflex-silverpoint-react 0.7.1__tar.gz → 0.8.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 (38) hide show
  1. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/PKG-INFO +36 -3
  2. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/README.md +34 -1
  3. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/__init__.py +1 -1
  4. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/base.py +1 -1
  5. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/base.pyi +1 -1
  6. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/catalog.py +12 -0
  7. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react.egg-info/PKG-INFO +36 -3
  8. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react.egg-info/SOURCES.txt +3 -0
  9. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/pyproject.toml +2 -2
  10. reflex_silverpoint_react-0.8.0/tests/test_agent_catalog.py +162 -0
  11. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/tests/test_components.py +4 -4
  12. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/tests/test_demo_data.py +23 -0
  13. reflex_silverpoint_react-0.8.0/tests/test_plugin.py +42 -0
  14. reflex_silverpoint_react-0.8.0/tests/test_skill.py +170 -0
  15. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/tests/test_v07.py +5 -3
  16. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/LICENSE +0 -0
  17. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/assets/trigger.jsx +0 -0
  18. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/charts.py +0 -0
  19. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/charts.pyi +0 -0
  20. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/composed.py +0 -0
  21. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/composed.pyi +0 -0
  22. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/dashboard.py +0 -0
  23. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/dashboard.pyi +0 -0
  24. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/helpers.py +0 -0
  25. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/overlays.py +0 -0
  26. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/overlays.pyi +0 -0
  27. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/palette.py +0 -0
  28. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/surfaces.py +0 -0
  29. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/surfaces.pyi +0 -0
  30. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/timeline.py +0 -0
  31. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/timeline.pyi +0 -0
  32. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/ui.py +0 -0
  33. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/ui.pyi +0 -0
  34. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/ui_catalog.py +0 -0
  35. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react.egg-info/dependency_links.txt +0 -0
  36. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react.egg-info/requires.txt +0 -0
  37. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react.egg-info/top_level.txt +0 -0
  38. {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/setup.cfg +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reflex-silverpoint-react
3
- Version: 0.7.1
4
- Summary: The 60 silverpoint charts, the Dashboard, 38 UI, composed and overlay components, palettes and a Knight Lab timeline (Renaissance silverpoint drawing style) for Reflex, wrapping @silverpoint/react 0.7
3
+ Version: 0.8.0
4
+ Summary: The 60 silverpoint charts, the Dashboard, 38 UI, composed and overlay components, palettes and a Knight Lab timeline (Renaissance silverpoint drawing style) for Reflex, wrapping @silverpoint/react 0.8
5
5
  Author-email: Ernesto Crespo <ecrespo@gmail.com>
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/ecrespo/reflex-silverpoint-react
@@ -34,7 +34,7 @@ prepared ground, tone built from hatching, and white heightening on the live val
34
34
  The hand-drawn irregularity lives only in the ornament. The data geometry is exact, and every chart
35
35
  has a `precision` mode that turns the inking off.
36
36
 
37
- It wraps [`@silverpoint/react`](https://www.npmjs.com/package/@silverpoint/react) **0.7.0** and exposes
37
+ It wraps [`@silverpoint/react`](https://www.npmjs.com/package/@silverpoint/react) **0.8.0** and exposes
38
38
  **all 60 charts**, the **`Dashboard`** (a laid-out, linkable grid of charts), the **17 UI
39
39
  components** (buttons, inputs, tabs, cards, alerts…), the **12 composed components** (table,
40
40
  pagination, breadcrumb, avatar…), the **7 overlays** and the toaster (modal, drawer, popover,
@@ -45,6 +45,14 @@ readouts and the imperative handle (SVG export and geometry) as Python.
45
45
  Everything it renders is an npm component of silverpoint, under its parity contract: the same
46
46
  markup React, Vue and Angular render.
47
47
 
48
+ ### New in 0.8.0
49
+
50
+ - Wraps **`@silverpoint/react` 0.8.0**. silverpoint 0.8 adds agent access and changes no API this
51
+ package wraps, so the Python builders are unchanged.
52
+ - **Agent access:** the `reflex-silverpoint` skill, a Claude plugin with the hosted silverpoint MCP
53
+ server, and `reflex-catalog.json`, the Reflex catalog silverpoint's agent catalog joins. See
54
+ [Use with AI](#use-with-ai).
55
+
48
56
  ### New in 0.7.1
49
57
 
50
58
  - Wraps **`@silverpoint/react` 0.7.0** (0.7.0 wrapped 0.6.0). silverpoint 0.7 took this package's
@@ -587,6 +595,31 @@ The exported SVG carries its own ground colours, so it renders the same outside
587
595
  - Accessors are field names (strings). The JavaScript API also accepts functions; from Python, shape
588
596
  your rows instead.
589
597
 
598
+ ## Use with AI
599
+
600
+ An assistant writes better `reflex-silverpoint-react` code with the **`reflex-silverpoint` skill**
601
+ ([`skills/reflex-silverpoint/`](skills/reflex-silverpoint/SKILL.md)): the builders and their
602
+ snake_case props, State patterns, event payloads, the provider and palettes, composed components and
603
+ overlays, the dashboard and the timeline. Its tables are generated from the package, and every Python
604
+ example in it is imported and rendered by the tests.
605
+
606
+ In Claude Code, the plugin installs the skill together with the hosted silverpoint MCP server
607
+ (`https://silverpointweb.vercel.app/mcp`), which recommends charts, checks props and draws a chart
608
+ as SVG before you write the code:
609
+
610
+ ```text
611
+ /plugin marketplace add ecrespo/reflex-silverpoint-react
612
+ /plugin install reflex-silverpoint@reflex-silverpoint
613
+ ```
614
+
615
+ The MCP server alone: `claude mcp add --transport http silverpoint https://silverpointweb.vercel.app/mcp`.
616
+
617
+ [`reflex-catalog.json`](reflex-catalog.json) is this package's catalog for agents: every chart and
618
+ component builder with its props (Python name, JS name and type), the deprecated arguments it still
619
+ accepts, the helpers, the package version and the silverpoint version it wraps. silverpoint's agent
620
+ catalog joins it, so its MCP tools answer with the Reflex names too. `scripts/export_catalog.py`
621
+ writes it, CI fails when it is stale, and each GitHub release carries it.
622
+
590
623
  ## Demo app
591
624
 
592
625
  ```bash
@@ -7,7 +7,7 @@ prepared ground, tone built from hatching, and white heightening on the live val
7
7
  The hand-drawn irregularity lives only in the ornament. The data geometry is exact, and every chart
8
8
  has a `precision` mode that turns the inking off.
9
9
 
10
- It wraps [`@silverpoint/react`](https://www.npmjs.com/package/@silverpoint/react) **0.7.0** and exposes
10
+ It wraps [`@silverpoint/react`](https://www.npmjs.com/package/@silverpoint/react) **0.8.0** and exposes
11
11
  **all 60 charts**, the **`Dashboard`** (a laid-out, linkable grid of charts), the **17 UI
12
12
  components** (buttons, inputs, tabs, cards, alerts…), the **12 composed components** (table,
13
13
  pagination, breadcrumb, avatar…), the **7 overlays** and the toaster (modal, drawer, popover,
@@ -18,6 +18,14 @@ readouts and the imperative handle (SVG export and geometry) as Python.
18
18
  Everything it renders is an npm component of silverpoint, under its parity contract: the same
19
19
  markup React, Vue and Angular render.
20
20
 
21
+ ### New in 0.8.0
22
+
23
+ - Wraps **`@silverpoint/react` 0.8.0**. silverpoint 0.8 adds agent access and changes no API this
24
+ package wraps, so the Python builders are unchanged.
25
+ - **Agent access:** the `reflex-silverpoint` skill, a Claude plugin with the hosted silverpoint MCP
26
+ server, and `reflex-catalog.json`, the Reflex catalog silverpoint's agent catalog joins. See
27
+ [Use with AI](#use-with-ai).
28
+
21
29
  ### New in 0.7.1
22
30
 
23
31
  - Wraps **`@silverpoint/react` 0.7.0** (0.7.0 wrapped 0.6.0). silverpoint 0.7 took this package's
@@ -560,6 +568,31 @@ The exported SVG carries its own ground colours, so it renders the same outside
560
568
  - Accessors are field names (strings). The JavaScript API also accepts functions; from Python, shape
561
569
  your rows instead.
562
570
 
571
+ ## Use with AI
572
+
573
+ An assistant writes better `reflex-silverpoint-react` code with the **`reflex-silverpoint` skill**
574
+ ([`skills/reflex-silverpoint/`](skills/reflex-silverpoint/SKILL.md)): the builders and their
575
+ snake_case props, State patterns, event payloads, the provider and palettes, composed components and
576
+ overlays, the dashboard and the timeline. Its tables are generated from the package, and every Python
577
+ example in it is imported and rendered by the tests.
578
+
579
+ In Claude Code, the plugin installs the skill together with the hosted silverpoint MCP server
580
+ (`https://silverpointweb.vercel.app/mcp`), which recommends charts, checks props and draws a chart
581
+ as SVG before you write the code:
582
+
583
+ ```text
584
+ /plugin marketplace add ecrespo/reflex-silverpoint-react
585
+ /plugin install reflex-silverpoint@reflex-silverpoint
586
+ ```
587
+
588
+ The MCP server alone: `claude mcp add --transport http silverpoint https://silverpointweb.vercel.app/mcp`.
589
+
590
+ [`reflex-catalog.json`](reflex-catalog.json) is this package's catalog for agents: every chart and
591
+ component builder with its props (Python name, JS name and type), the deprecated arguments it still
592
+ accepts, the helpers, the package version and the silverpoint version it wraps. silverpoint's agent
593
+ catalog joins it, so its MCP tools answer with the Reflex names too. `scripts/export_catalog.py`
594
+ writes it, CI fails when it is stale, and each GitHub release carries it.
595
+
563
596
  ## Demo app
564
597
 
565
598
  ```bash
@@ -1,7 +1,7 @@
1
1
  """reflex-silverpoint-react: silverpoint charts and UI components for Reflex.
2
2
 
3
3
  Charts and interface components drawn in the manner of a Renaissance silverpoint drawing,
4
- wrapping ``@silverpoint/react`` 0.7.0: the 60 charts, the dashboard, the 38 UI, composed and overlay
4
+ wrapping ``@silverpoint/react`` 0.8.0: the 60 charts, the dashboard, the 38 UI, composed and overlay
5
5
  components, the palettes and the Knight Lab timeline.
6
6
  """
7
7
 
@@ -20,7 +20,7 @@ import reflex as rx
20
20
  from .palette import PaletteLike, apply_palette
21
21
 
22
22
  #: The silverpoint npm release these wrappers are written against.
23
- SILVERPOINT_VERSION = "0.7.0"
23
+ SILVERPOINT_VERSION = "0.8.0"
24
24
 
25
25
  #: The npm package every chart is imported from (the barrel; it is side-effect free, so the
26
26
  #: bundler keeps only the charts a page uses).
@@ -15,7 +15,7 @@ from reflex_components_core.core.breakpoints import Breakpoints
15
15
 
16
16
  from .palette import Palette, PaletteLike
17
17
 
18
- SILVERPOINT_VERSION = "0.7.0"
18
+ SILVERPOINT_VERSION = "0.8.0"
19
19
  SILVERPOINT_LIBRARY = ...
20
20
  SILVERPOINT_STYLESHEETS = ("@silverpoint/fonts/fonts.css", "@silverpoint/grounds/styles.css")
21
21
  GroundName: TypeAlias = Literal["silverpoint", "cyanotype"]
@@ -912,6 +912,12 @@ CHARTS: tuple[ChartInfo, ...] = (
912
912
  PropDoc(
913
913
  "root_id", "rootId", "string | null", "The node drawn as root, controlled; the data root by default."
914
914
  ),
915
+ PropDoc(
916
+ "on_root_change",
917
+ "onRootChange",
918
+ "(id: string | null) => void",
919
+ "A node asked to be drawn as root (`None`: back to the top); pass it back as `root_id`.",
920
+ ),
915
921
  ),
916
922
  ),
917
923
  ChartInfo(
@@ -934,6 +940,12 @@ CHARTS: tuple[ChartInfo, ...] = (
934
940
  PropDoc(
935
941
  "root_id", "rootId", "string | null", "The node drawn as root, controlled; the data root by default."
936
942
  ),
943
+ PropDoc(
944
+ "on_root_change",
945
+ "onRootChange",
946
+ "(id: string | null) => void",
947
+ "A node asked to be drawn as root (`None`: back to the top); pass it back as `root_id`.",
948
+ ),
937
949
  ),
938
950
  ),
939
951
  ChartInfo(
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reflex-silverpoint-react
3
- Version: 0.7.1
4
- Summary: The 60 silverpoint charts, the Dashboard, 38 UI, composed and overlay components, palettes and a Knight Lab timeline (Renaissance silverpoint drawing style) for Reflex, wrapping @silverpoint/react 0.7
3
+ Version: 0.8.0
4
+ Summary: The 60 silverpoint charts, the Dashboard, 38 UI, composed and overlay components, palettes and a Knight Lab timeline (Renaissance silverpoint drawing style) for Reflex, wrapping @silverpoint/react 0.8
5
5
  Author-email: Ernesto Crespo <ecrespo@gmail.com>
6
6
  License: MIT
7
7
  Project-URL: Homepage, https://github.com/ecrespo/reflex-silverpoint-react
@@ -34,7 +34,7 @@ prepared ground, tone built from hatching, and white heightening on the live val
34
34
  The hand-drawn irregularity lives only in the ornament. The data geometry is exact, and every chart
35
35
  has a `precision` mode that turns the inking off.
36
36
 
37
- It wraps [`@silverpoint/react`](https://www.npmjs.com/package/@silverpoint/react) **0.7.0** and exposes
37
+ It wraps [`@silverpoint/react`](https://www.npmjs.com/package/@silverpoint/react) **0.8.0** and exposes
38
38
  **all 60 charts**, the **`Dashboard`** (a laid-out, linkable grid of charts), the **17 UI
39
39
  components** (buttons, inputs, tabs, cards, alerts…), the **12 composed components** (table,
40
40
  pagination, breadcrumb, avatar…), the **7 overlays** and the toaster (modal, drawer, popover,
@@ -45,6 +45,14 @@ readouts and the imperative handle (SVG export and geometry) as Python.
45
45
  Everything it renders is an npm component of silverpoint, under its parity contract: the same
46
46
  markup React, Vue and Angular render.
47
47
 
48
+ ### New in 0.8.0
49
+
50
+ - Wraps **`@silverpoint/react` 0.8.0**. silverpoint 0.8 adds agent access and changes no API this
51
+ package wraps, so the Python builders are unchanged.
52
+ - **Agent access:** the `reflex-silverpoint` skill, a Claude plugin with the hosted silverpoint MCP
53
+ server, and `reflex-catalog.json`, the Reflex catalog silverpoint's agent catalog joins. See
54
+ [Use with AI](#use-with-ai).
55
+
48
56
  ### New in 0.7.1
49
57
 
50
58
  - Wraps **`@silverpoint/react` 0.7.0** (0.7.0 wrapped 0.6.0). silverpoint 0.7 took this package's
@@ -587,6 +595,31 @@ The exported SVG carries its own ground colours, so it renders the same outside
587
595
  - Accessors are field names (strings). The JavaScript API also accepts functions; from Python, shape
588
596
  your rows instead.
589
597
 
598
+ ## Use with AI
599
+
600
+ An assistant writes better `reflex-silverpoint-react` code with the **`reflex-silverpoint` skill**
601
+ ([`skills/reflex-silverpoint/`](skills/reflex-silverpoint/SKILL.md)): the builders and their
602
+ snake_case props, State patterns, event payloads, the provider and palettes, composed components and
603
+ overlays, the dashboard and the timeline. Its tables are generated from the package, and every Python
604
+ example in it is imported and rendered by the tests.
605
+
606
+ In Claude Code, the plugin installs the skill together with the hosted silverpoint MCP server
607
+ (`https://silverpointweb.vercel.app/mcp`), which recommends charts, checks props and draws a chart
608
+ as SVG before you write the code:
609
+
610
+ ```text
611
+ /plugin marketplace add ecrespo/reflex-silverpoint-react
612
+ /plugin install reflex-silverpoint@reflex-silverpoint
613
+ ```
614
+
615
+ The MCP server alone: `claude mcp add --transport http silverpoint https://silverpointweb.vercel.app/mcp`.
616
+
617
+ [`reflex-catalog.json`](reflex-catalog.json) is this package's catalog for agents: every chart and
618
+ component builder with its props (Python name, JS name and type), the deprecated arguments it still
619
+ accepts, the helpers, the package version and the silverpoint version it wraps. silverpoint's agent
620
+ catalog joins it, so its MCP tools answer with the Reflex names too. `scripts/export_catalog.py`
621
+ writes it, CI fails when it is stale, and each GitHub release carries it.
622
+
590
623
  ## Demo app
591
624
 
592
625
  ```bash
@@ -28,6 +28,9 @@ custom_components/reflex_silverpoint_react.egg-info/dependency_links.txt
28
28
  custom_components/reflex_silverpoint_react.egg-info/requires.txt
29
29
  custom_components/reflex_silverpoint_react.egg-info/top_level.txt
30
30
  custom_components/reflex_silverpoint_react/assets/trigger.jsx
31
+ tests/test_agent_catalog.py
31
32
  tests/test_components.py
32
33
  tests/test_demo_data.py
34
+ tests/test_plugin.py
35
+ tests/test_skill.py
33
36
  tests/test_v07.py
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "reflex-silverpoint-react"
7
- version = "0.7.1"
8
- description = "The 60 silverpoint charts, the Dashboard, 38 UI, composed and overlay components, palettes and a Knight Lab timeline (Renaissance silverpoint drawing style) for Reflex, wrapping @silverpoint/react 0.7"
7
+ version = "0.8.0"
8
+ description = "The 60 silverpoint charts, the Dashboard, 38 UI, composed and overlay components, palettes and a Knight Lab timeline (Renaissance silverpoint drawing style) for Reflex, wrapping @silverpoint/react 0.8"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
11
11
  requires-python = ">=3.11"
@@ -0,0 +1,162 @@
1
+ """Feature-005 (silverpoint): the Reflex catalog export, ``reflex-catalog.json`` (Data Model §2.30).
2
+
3
+ ``scripts/export_catalog.py`` builds it from the package itself; silverpoint's agent catalog joins
4
+ it by slug and JS prop name, and marks every Reflex entry stale when ``wraps`` is not its version.
5
+ """
6
+
7
+ import importlib.metadata
8
+ import importlib.util
9
+ import json
10
+ import warnings
11
+ from pathlib import Path
12
+
13
+ import pytest
14
+ import reflex_silverpoint_react as sp
15
+
16
+ ROOT = Path(__file__).resolve().parent.parent
17
+ FIXTURES = Path(__file__).parent / "fixtures"
18
+
19
+
20
+ def _load(name: str):
21
+ spec = importlib.util.spec_from_file_location(name, ROOT / "scripts" / f"{name}.py")
22
+ module = importlib.util.module_from_spec(spec)
23
+ spec.loader.exec_module(module)
24
+ return module
25
+
26
+
27
+ export_catalog = _load("export_catalog")
28
+
29
+
30
+ @pytest.fixture(scope="module")
31
+ def catalog() -> dict:
32
+ return export_catalog.build()
33
+
34
+
35
+ def _builders(catalog: dict) -> set[str]:
36
+ return {c["builder"] for c in catalog["charts"]} | {c["builder"] for c in catalog["components"]}
37
+
38
+
39
+ def test_req553_catalog_header(catalog):
40
+ assert catalog["schema"] == 1
41
+ assert catalog["package"] == "reflex-silverpoint-react"
42
+ assert catalog["version"] == importlib.metadata.version("reflex-silverpoint-react")
43
+ assert list(catalog) == ["schema", "package", "version", "wraps", "common", "charts", "components", "helpers"]
44
+
45
+
46
+ def test_req556_wraps_is_silverpoint_version(catalog):
47
+ assert catalog["wraps"] == sp.SILVERPOINT_VERSION
48
+
49
+
50
+ def test_req556_export_is_not_a_derived_pin(catalog):
51
+ # `derived` marks silverpoint's interim pin; the wrapper's own export never carries it.
52
+ assert "derived" not in catalog
53
+
54
+
55
+ def test_req553_every_builder_in_all_is_present(catalog):
56
+ named = _builders(catalog) | set(catalog["helpers"]) | {"sp_trigger"}
57
+ lowercase = {name for name in sp.__all__ if name[0].islower()}
58
+ assert lowercase == named
59
+ assert {info.factory_name for info in sp.CHARTS} <= _builders(catalog)
60
+ assert {f"sp_{info.slug.replace('-', '_')}" for info in sp.UI_COMPONENTS} <= _builders(catalog)
61
+ assert not {name for name in catalog["helpers"] if name.startswith("sp_")}
62
+
63
+
64
+ def test_req553_chart_slugs_match_silverpoint(catalog):
65
+ silverpoint = json.loads((FIXTURES / "silverpoint-slugs.json").read_text())
66
+ slugs = [c["slug"] for c in catalog["charts"]]
67
+ # The same 60 slugs; the order is each catalog's own (by family here, by phase there).
68
+ assert len(slugs) == len(set(slugs)) == 60
69
+ assert set(slugs) == set(silverpoint["charts"])
70
+
71
+
72
+ def test_req553_component_slugs_match_silverpoint(catalog):
73
+ silverpoint = json.loads((FIXTURES / "silverpoint-slugs.json").read_text())
74
+ # Besides silverpoint's 38 components: the dashboard and the timeline (catalog entries of their
75
+ # own there) and the parts a component is built from, which Reflex exposes as builders.
76
+ parts = {"dashboard", "timeline", "avatar-group", "collapse-item", "tab-panel", "toaster"}
77
+ assert {c["slug"] for c in catalog["components"]} == set(silverpoint["components"]) | parts
78
+
79
+
80
+ def test_req553_props_carry_python_and_js_names(catalog):
81
+ for entry in [*catalog["charts"], *catalog["components"]]:
82
+ names = [p["name"] for p in entry["props"]]
83
+ assert len(names) == len(set(names)), entry["slug"]
84
+ for prop in entry["props"]:
85
+ assert set(prop) == {"name", "js", "type"}
86
+ assert prop["name"] == prop["name"].lower(), prop
87
+ line = next(c for c in catalog["charts"] if c["slug"] == "line-chart")
88
+ assert {"name": "x_key", "js": "xKey", "type": "Accessor<string | number>"} in line["props"]
89
+ assert [p["name"] for p in catalog["common"]["chart"]][:2] == ["data", "ground"]
90
+ assert "palette" in {p["name"] for p in catalog["common"]["ui"]}
91
+
92
+
93
+ def test_req553_drillable_charts_list_on_root_change(catalog):
94
+ # Declared on a private base class, so only the chart's PropDocs can name it.
95
+ event = {"name": "on_root_change", "js": "onRootChange", "type": "(id: string | null) => void"}
96
+ for slug in ("squarified-treemap", "sunburst-chart"):
97
+ chart = next(c for c in catalog["charts"] if c["slug"] == slug)
98
+ assert event in chart["props"], slug
99
+
100
+
101
+ def test_req553_deprecated_arguments_are_listed(catalog):
102
+ deprecated = {c["builder"]: c["deprecated"] for c in catalog["components"] if c["deprecated"]}
103
+ assert deprecated == {
104
+ "sp_drawer": {"size": "extent"},
105
+ "sp_input_number": {"input_id": "id"},
106
+ "sp_popover": {"side_offset": "offset"},
107
+ "sp_textarea": {"textarea_id": "id"},
108
+ "sp_toast": {"action_alt": ""},
109
+ "sp_tooltip": {"side_offset": "offset"},
110
+ }
111
+
112
+
113
+ #: What each builder needs besides the argument under test, and a value for it.
114
+ _REQUIRED: dict[str, tuple[tuple, dict]] = {
115
+ "sp_popover": (("Body",), {"trigger": "Open"}),
116
+ "sp_tooltip": (("Hover", "Tip"), {}),
117
+ "sp_toast": (("Saved",), {}),
118
+ }
119
+ _VALUES = {"extent": 320, "id": "field", "offset": 9}
120
+
121
+
122
+ @pytest.mark.parametrize(
123
+ ("builder", "old", "new"),
124
+ [(builder, old, new) for builder, names in export_catalog.DEPRECATED.items() for old, new in names.items()],
125
+ )
126
+ def test_req553_deprecated_arguments_still_mean_their_new_name(builder, old, new):
127
+ build = getattr(sp, builder)
128
+ args, kwargs = _REQUIRED.get(builder, ((), {}))
129
+ with warnings.catch_warnings():
130
+ warnings.simplefilter("ignore", DeprecationWarning)
131
+ if not new: # ignored: the build is the same as without it
132
+ assert str(build(*args, **kwargs, **{old: "x"}).render()) == str(build(*args, **kwargs).render())
133
+ return
134
+ value = _VALUES[new]
135
+ old_render = build(*args, **kwargs, **{old: value}).render()
136
+ assert str(old_render) == str(build(*args, **kwargs, **{new: value}).render())
137
+
138
+
139
+ def test_req553_export_equals_silverpoint_derived_pin(catalog):
140
+ """While the two agree: silverpoint's interim pin, read from this repository with ``ast``.
141
+
142
+ The pin (tests/fixtures/reflex-catalog.derived.json, silverpoint v0.8.0) was derived from 0.7.1
143
+ wrapping 0.7.0; its reader cannot see a builder's deprecated arguments, and its chart props came
144
+ from the PropDocs before they named the drill-down event.
145
+ """
146
+ pin = json.loads((FIXTURES / "reflex-catalog.derived.json").read_text())
147
+ derived = pin.pop("derived")
148
+ assert derived
149
+ ours = json.loads(json.dumps(catalog))
150
+ for entry in ours["components"]:
151
+ entry["deprecated"] = {}
152
+ for entry in ours["charts"]:
153
+ entry["props"] = [p for p in entry["props"] if p["name"] != "on_root_change"]
154
+ for key in ("version", "wraps"):
155
+ pin.pop(key)
156
+ ours.pop(key)
157
+ assert ours == pin
158
+
159
+
160
+ def test_req553_committed_catalog_is_current(catalog):
161
+ committed = (ROOT / "reflex-catalog.json").read_text()
162
+ assert committed == export_catalog.dumps(catalog), "run: uv run python scripts/export_catalog.py"
@@ -10,7 +10,7 @@ def _props(component: rx.Component) -> dict[str, str]:
10
10
  return dict(prop.split(":", 1) for prop in rendered["props"])
11
11
 
12
12
 
13
- #: Every chart @silverpoint/react 0.7.0 exports, as its README lists them ("The 60 charts").
13
+ #: Every chart @silverpoint/react 0.8.0 exports, as its README lists them ("The 60 charts").
14
14
  UPSTREAM_CHARTS_0_6 = {
15
15
  "LineChart", "StepChart", "SparklineRows", "KpiCard", "BarChart", "StackedBarChart",
16
16
  "ComposedChart", "WaterfallChart", "FunnelChart", "BulletChart", "PyramidChart",
@@ -105,9 +105,9 @@ def test_provider() -> None:
105
105
  assert _props(component)["locale"] == '"es-VE"'
106
106
 
107
107
 
108
- def test_version_pins_silverpoint_0_6() -> None:
109
- assert sp.SILVERPOINT_VERSION == "0.7.0"
110
- assert sp.SILVERPOINT_LIBRARY == "@silverpoint/react@0.7.0"
108
+ def test_version_pins_silverpoint() -> None:
109
+ assert sp.SILVERPOINT_VERSION == "0.8.0"
110
+ assert sp.SILVERPOINT_LIBRARY == "@silverpoint/react@0.8.0"
111
111
 
112
112
 
113
113
  def test_cyanotype_ground() -> None:
@@ -83,3 +83,26 @@ def test_drill_path_names_the_nodes_from_the_root() -> None:
83
83
  assert drill_path(BUDGET, parent["id"])[-1] == parent["name"]
84
84
  assert drill_path(BUDGET, parent["id"])[0] == root["name"]
85
85
  assert drill_path(BUDGET, "no-such-node") == [root["name"]]
86
+
87
+
88
+ #: The first vite with the fixes for GHSA-587h-7wmj-h5pm, GHSA-r825-w8mm-5pxw and GHSA-698v-4r3x-3vcv.
89
+ VITE_PATCHED = (8, 3, 3)
90
+
91
+
92
+ def _version(spec: str) -> tuple[int, ...]:
93
+ return tuple(int(part) for part in spec.lstrip("^~=v").split("."))
94
+
95
+
96
+ def test_demo_pins_a_patched_vite():
97
+ """The demo's config and its persisted package.json both pin a vite with the advisories fixed."""
98
+ import json
99
+
100
+ from reflex_base.constants.installer import PackageJson
101
+
102
+ demo = Path(__file__).resolve().parents[1] / "silverpoint_react_demo"
103
+ sys.path.insert(0, str(demo))
104
+ import rxconfig # noqa: F401 (raises the framework pin)
105
+
106
+ assert _version(PackageJson.DEV_DEPENDENCIES["vite"]) >= VITE_PATCHED
107
+ persisted = json.loads((demo / "reflex.lock" / "package.json").read_text(encoding="utf-8"))
108
+ assert _version(persisted["devDependencies"]["vite"]) >= VITE_PATCHED
@@ -0,0 +1,42 @@
1
+ """Feature-005 (silverpoint): the Claude plugin of this repository (T-248, REQ-555).
2
+
3
+ One plugin, `reflex-silverpoint`: the skill and the hosted silverpoint MCP server, declared inline in
4
+ `plugin.json` (a root `.mcp.json` would connect every session opened in this repository).
5
+ """
6
+
7
+ import json
8
+ import tomllib
9
+ from pathlib import Path
10
+
11
+ ROOT = Path(__file__).resolve().parent.parent
12
+ PLUGIN_DIR = ROOT / ".claude-plugin"
13
+ VERSION = tomllib.loads((ROOT / "pyproject.toml").read_text())["project"]["version"]
14
+
15
+
16
+ def _read(name: str) -> dict:
17
+ return json.loads((PLUGIN_DIR / name).read_text())
18
+
19
+
20
+ def test_req555_marketplace_lists_one_plugin_from_the_repository_root():
21
+ marketplace = _read("marketplace.json")
22
+ assert marketplace["name"] == "reflex-silverpoint"
23
+ [plugin] = marketplace["plugins"]
24
+ assert plugin["name"] == "reflex-silverpoint"
25
+ assert plugin["source"] == "./"
26
+
27
+
28
+ def test_req555_plugin_holds_the_skill_and_the_hosted_server():
29
+ plugin = _read("plugin.json")
30
+ assert plugin["name"] == "reflex-silverpoint"
31
+ assert plugin["skills"] == ["./skills/reflex-silverpoint"]
32
+ assert (ROOT / "skills" / "reflex-silverpoint" / "SKILL.md").is_file()
33
+ assert plugin["mcpServers"] == {"silverpoint": {"type": "http", "url": "https://silverpointweb.vercel.app/mcp"}}
34
+
35
+
36
+ def test_req555_no_root_mcp_json():
37
+ assert not (ROOT / ".mcp.json").exists()
38
+
39
+
40
+ def test_req555_plugin_versions_are_the_package_version():
41
+ assert _read("plugin.json")["version"] == VERSION
42
+ assert [p["version"] for p in _read("marketplace.json")["plugins"]] == [VERSION]
@@ -0,0 +1,170 @@
1
+ """Feature-005 (silverpoint): the `reflex-silverpoint` skill (T-246) and its examples check (T-247).
2
+
3
+ ``scripts/make_skill.py`` writes ``skills/reflex-silverpoint/`` from the hand-written templates and
4
+ ``reflex-catalog.json``. Every ``python`` fence of the skill is imported and the components it builds
5
+ are rendered (REQ-557); every other fence is prose.
6
+ """
7
+
8
+ import ast
9
+ import importlib.util
10
+ import inspect
11
+ import json
12
+ import re
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ import pytest
17
+ import reflex as rx
18
+
19
+ ROOT = Path(__file__).resolve().parent.parent
20
+ SKILL = ROOT / "skills" / "reflex-silverpoint"
21
+ FIXTURES = Path(__file__).parent / "fixtures"
22
+
23
+ _spec = importlib.util.spec_from_file_location("make_skill", ROOT / "scripts" / "make_skill.py")
24
+ make_skill = importlib.util.module_from_spec(_spec)
25
+ _spec.loader.exec_module(make_skill)
26
+
27
+ CATALOG = json.loads((ROOT / "reflex-catalog.json").read_text())
28
+ DESCRIPTION = (
29
+ "Build silverpoint charts, dashboards and UI in Python with Reflex through reflex-silverpoint-react. "
30
+ "Use whenever code imports reflex_silverpoint_react or the user wants silverpoint in Reflex."
31
+ )
32
+
33
+
34
+ def _files() -> dict[str, str]:
35
+ return {str(p.relative_to(SKILL)): p.read_text() for p in sorted(SKILL.rglob("*.md")) if "templates" not in p.parts}
36
+
37
+
38
+ # ── Layout and contents ─────────────────────────────────────────────────────────────────
39
+
40
+
41
+ def test_req553_skill_is_current():
42
+ assert make_skill.render() == _files(), "run: uv run python scripts/make_skill.py"
43
+
44
+
45
+ def test_req553_skill_layout():
46
+ families = [f["slug"] for f in json.loads((FIXTURES / "silverpoint-slugs.json").read_text())["families"]]
47
+ expected = {"SKILL.md", *(f"references/charts/{slug}.md" for slug in families)}
48
+ expected |= {
49
+ f"references/{name}.md"
50
+ for name in ("components", "overlays", "timeline", "dashboard", "palettes", "state-and-events")
51
+ }
52
+ assert set(_files()) == expected
53
+
54
+
55
+ def test_req554_skill_md_frontmatter_and_length():
56
+ text = (SKILL / "SKILL.md").read_text()
57
+ assert text.startswith(f"---\nname: reflex-silverpoint\ndescription: {DESCRIPTION}\n---\n")
58
+ assert len(text.splitlines()) < 500
59
+
60
+
61
+ def test_req554_every_file_names_the_version_it_describes():
62
+ stamp = f"reflex-silverpoint-react {CATALOG['version']}"
63
+ for path, text in _files().items():
64
+ assert stamp in text, path
65
+ assert f"silverpoint {CATALOG['wraps']}" in text, path
66
+
67
+
68
+ def test_req554_skill_prefers_the_mcp_tools():
69
+ text = (SKILL / "SKILL.md").read_text()
70
+ assert "## Prefer the silverpoint MCP tools when they are connected" in text
71
+ assert "https://silverpointweb.vercel.app/mcp" in text
72
+
73
+
74
+ def test_req553_every_builder_and_helper_is_documented():
75
+ text = "\n".join(_files().values())
76
+ for name in [c["builder"] for c in CATALOG["charts"] + CATALOG["components"]] + CATALOG["helpers"]:
77
+ assert f"`{name}`" in text, name
78
+
79
+
80
+ def test_req553_chart_props_tables_list_python_and_js_names():
81
+ lines = (SKILL / "references" / "charts" / "lines.md").read_text()
82
+ assert "| `x_key` | `xKey` | `Accessor<string \\| number>` |" in lines
83
+ assert "<!-- generated:family -->" in lines and "<!-- /generated:family -->" in lines
84
+
85
+
86
+ def test_req547_overlay_triggers_are_never_sp_button():
87
+ # The prose names the mistake; no example may make it.
88
+ for name, code in make_skill.extract_examples(_files()):
89
+ assert not re.search(r"trigger\s*=\s*sp_button", code), name
90
+ assert "Never `trigger=sp_button(...)`" in (SKILL / "SKILL.md").read_text()
91
+
92
+
93
+ # ── Examples (REQ-557) ──────────────────────────────────────────────────────────────────
94
+
95
+
96
+ def test_req557_extract_refuses_a_fence_that_is_neither_python_nor_prose():
97
+ with pytest.raises(ValueError, match="tsx"):
98
+ make_skill.extract_examples({"x.md": "```tsx\n<LineChart />\n```\n"})
99
+ with pytest.raises(ValueError, match="language"):
100
+ make_skill.extract_examples({"x.md": "```\nplain\n```\n"})
101
+ assert make_skill.extract_examples({"x.md": "```sh\npip install x\n```\n```json\n[]\n```\n"}) == []
102
+ found = make_skill.extract_examples({"a/b.md": "```python\nx = 1\n```\n"})
103
+ assert found == [("a-b-1", "x = 1\n")]
104
+
105
+
106
+ EXAMPLES = make_skill.extract_examples(_files())
107
+
108
+
109
+ def test_req557_the_skill_has_python_examples():
110
+ assert len(EXAMPLES) >= 60 + 10
111
+
112
+
113
+ def _components(module) -> list[rx.Component]:
114
+ """Every component the example builds: module-level ones, and what its views return."""
115
+ found = [v for v in vars(module).values() if isinstance(v, rx.Component)]
116
+ for fn in vars(module).values():
117
+ is_view = inspect.isfunction(fn) and fn.__module__ == module.__name__ and not fn.__name__.startswith("_")
118
+ if is_view and all(p.default is not p.empty for p in inspect.signature(fn).parameters.values()):
119
+ result = fn()
120
+ if isinstance(result, rx.Component):
121
+ found.append(result)
122
+ return found
123
+
124
+
125
+ #: Props Reflex gives every component, besides those the catalog lists.
126
+ _REFLEX_PROPS = {"id", "class_name", "style", "key", "custom_attrs", "on_click"}
127
+
128
+
129
+ def _allowed() -> dict[str, set[str]]:
130
+ chart = {p["name"] for p in CATALOG["common"]["chart"]} | _REFLEX_PROPS
131
+ ui = {p["name"] for p in CATALOG["common"]["ui"]} | _REFLEX_PROPS
132
+ allowed = {c["builder"]: {p["name"] for p in c["props"]} | chart for c in CATALOG["charts"]}
133
+ allowed |= {c["builder"]: {p["name"] for p in c["props"]} | ui for c in CATALOG["components"]}
134
+ return allowed
135
+
136
+
137
+ @pytest.mark.parametrize(("name", "code"), EXAMPLES, ids=[name for name, _ in EXAMPLES])
138
+ def test_req557_python_example_names_only_catalog_props(name, code):
139
+ # Reflex passes an unknown keyword through to the npm component without a word, so rendering
140
+ # alone does not catch a misspelt prop: every keyword of a builder call must be in the catalog.
141
+ allowed = _allowed()
142
+ tree = ast.parse(code)
143
+ imported = {
144
+ alias.asname or alias.name: alias.name
145
+ for node in ast.walk(tree)
146
+ if isinstance(node, ast.ImportFrom) and node.module == "reflex_silverpoint_react"
147
+ for alias in node.names
148
+ }
149
+ for node in ast.walk(tree):
150
+ if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and imported.get(node.func.id) in allowed:
151
+ builder = imported[node.func.id]
152
+ for keyword in node.keywords:
153
+ assert keyword.arg is None or keyword.arg in allowed[builder], f"{builder} takes no {keyword.arg}"
154
+
155
+
156
+ @pytest.mark.parametrize(("name", "code"), EXAMPLES, ids=[name for name, _ in EXAMPLES])
157
+ def test_req557_python_example_imports_and_renders(name, code, tmp_path):
158
+ path = tmp_path / f"skill_{name.replace('-', '_')}.py"
159
+ path.write_text(code)
160
+ spec = importlib.util.spec_from_file_location(path.stem, path)
161
+ module = importlib.util.module_from_spec(spec)
162
+ sys.modules[path.stem] = module
163
+ try:
164
+ spec.loader.exec_module(module)
165
+ components = _components(module)
166
+ assert components, f"{name} builds no component"
167
+ for component in components:
168
+ assert component.render()
169
+ finally:
170
+ sys.modules.pop(path.stem, None)
@@ -46,9 +46,11 @@ class _S(rx.State):
46
46
  # ── Version ─────────────────────────────────────────────────────────────────────────────
47
47
 
48
48
 
49
- def test_wraps_silverpoint_0_7_0() -> None:
50
- assert sp.SILVERPOINT_VERSION == "0.7.0"
51
- assert sp.SILVERPOINT_LIBRARY == "@silverpoint/react@0.7.0"
49
+ def test_wraps_silverpoint_0_8_0() -> None:
50
+ # 0.8.0 changed no API of @silverpoint/react, core, grounds or fonts (agent access: the MCP
51
+ # server and the skills); only their versions moved.
52
+ assert sp.SILVERPOINT_VERSION == "0.8.0"
53
+ assert sp.SILVERPOINT_LIBRARY == "@silverpoint/react@0.8.0"
52
54
 
53
55
 
54
56
  # ── Palettes ────────────────────────────────────────────────────────────────────────────