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.
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/PKG-INFO +36 -3
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/README.md +34 -1
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/__init__.py +1 -1
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/base.py +1 -1
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/base.pyi +1 -1
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/catalog.py +12 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react.egg-info/PKG-INFO +36 -3
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react.egg-info/SOURCES.txt +3 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/pyproject.toml +2 -2
- reflex_silverpoint_react-0.8.0/tests/test_agent_catalog.py +162 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/tests/test_components.py +4 -4
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/tests/test_demo_data.py +23 -0
- reflex_silverpoint_react-0.8.0/tests/test_plugin.py +42 -0
- reflex_silverpoint_react-0.8.0/tests/test_skill.py +170 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/tests/test_v07.py +5 -3
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/LICENSE +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/assets/trigger.jsx +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/charts.py +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/charts.pyi +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/composed.py +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/composed.pyi +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/dashboard.py +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/dashboard.pyi +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/helpers.py +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/overlays.py +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/overlays.pyi +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/palette.py +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/surfaces.py +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/surfaces.pyi +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/timeline.py +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/timeline.pyi +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/ui.py +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/ui.pyi +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react/ui_catalog.py +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react.egg-info/dependency_links.txt +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react.egg-info/requires.txt +0 -0
- {reflex_silverpoint_react-0.7.1 → reflex_silverpoint_react-0.8.0}/custom_components/reflex_silverpoint_react.egg-info/top_level.txt +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
+
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.
|
|
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
|
|
109
|
-
assert sp.SILVERPOINT_VERSION == "0.
|
|
110
|
-
assert sp.SILVERPOINT_LIBRARY == "@silverpoint/react@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
|
|
50
|
-
|
|
51
|
-
|
|
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 ────────────────────────────────────────────────────────────────────────────
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|