spinagram 0.1.0rc1__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.
- spinagram-0.1.0rc1/.github/workflows/ci.yml +90 -0
- spinagram-0.1.0rc1/.github/workflows/docs.yml +50 -0
- spinagram-0.1.0rc1/.github/workflows/release.yml +56 -0
- spinagram-0.1.0rc1/.gitignore +6 -0
- spinagram-0.1.0rc1/.specs/LESSONS.md +153 -0
- spinagram-0.1.0rc1/.specs/features/annotations/design.md +140 -0
- spinagram-0.1.0rc1/.specs/features/annotations/spec.md +230 -0
- spinagram-0.1.0rc1/.specs/features/annotations/tasks.md +563 -0
- spinagram-0.1.0rc1/.specs/features/annotations/validation.md +177 -0
- spinagram-0.1.0rc1/.specs/features/auto-layout/context.md +9 -0
- spinagram-0.1.0rc1/.specs/features/auto-layout/design.md +113 -0
- spinagram-0.1.0rc1/.specs/features/auto-layout/spec.md +69 -0
- spinagram-0.1.0rc1/.specs/features/auto-layout/tasks.md +17 -0
- spinagram-0.1.0rc1/.specs/features/behavior-diagrams/design.md +93 -0
- spinagram-0.1.0rc1/.specs/features/behavior-diagrams/spec.md +237 -0
- spinagram-0.1.0rc1/.specs/features/behavior-diagrams/tasks.md +241 -0
- spinagram-0.1.0rc1/.specs/features/behavior-diagrams/validation.md +151 -0
- spinagram-0.1.0rc1/.specs/features/catalog-previews/spec.md +100 -0
- spinagram-0.1.0rc1/.specs/features/catalog-previews/tasks.md +280 -0
- spinagram-0.1.0rc1/.specs/features/catalog-previews/validation.md +150 -0
- spinagram-0.1.0rc1/.specs/features/css-inspector/spec.md +112 -0
- spinagram-0.1.0rc1/.specs/features/css-inspector/tasks.md +196 -0
- spinagram-0.1.0rc1/.specs/features/css-inspector/validation.md +109 -0
- spinagram-0.1.0rc1/.specs/features/css-styling/design.md +76 -0
- spinagram-0.1.0rc1/.specs/features/css-styling/spec.md +223 -0
- spinagram-0.1.0rc1/.specs/features/css-styling/tasks.md +206 -0
- spinagram-0.1.0rc1/.specs/features/css-styling/validation.md +128 -0
- spinagram-0.1.0rc1/.specs/features/diagram-catalog/design.md +168 -0
- spinagram-0.1.0rc1/.specs/features/diagram-catalog/spec.md +205 -0
- spinagram-0.1.0rc1/.specs/features/diagram-catalog/tasks.md +360 -0
- spinagram-0.1.0rc1/.specs/features/diagram-catalog/validation.md +200 -0
- spinagram-0.1.0rc1/.specs/features/diagram-import/context.md +64 -0
- spinagram-0.1.0rc1/.specs/features/diagram-import/design.md +246 -0
- spinagram-0.1.0rc1/.specs/features/diagram-import/spec.md +289 -0
- spinagram-0.1.0rc1/.specs/features/engine-v1/design.md +31 -0
- spinagram-0.1.0rc1/.specs/features/engine-v1/spec.md +26 -0
- spinagram-0.1.0rc1/.specs/features/engine-v1/tasks.md +20 -0
- spinagram-0.1.0rc1/.specs/features/layered-layout/design.md +95 -0
- spinagram-0.1.0rc1/.specs/features/layered-layout/spec.md +188 -0
- spinagram-0.1.0rc1/.specs/features/layered-layout/tasks.md +149 -0
- spinagram-0.1.0rc1/.specs/features/layered-layout/validation.md +137 -0
- spinagram-0.1.0rc1/.specs/features/legend-css/spec.md +114 -0
- spinagram-0.1.0rc1/.specs/features/legend-css/tasks.md +237 -0
- spinagram-0.1.0rc1/.specs/features/legend-css/validation.md +164 -0
- spinagram-0.1.0rc1/.specs/features/live-ui/context.md +43 -0
- spinagram-0.1.0rc1/.specs/features/live-ui/design.md +78 -0
- spinagram-0.1.0rc1/.specs/features/live-ui/spec.md +132 -0
- spinagram-0.1.0rc1/.specs/features/live-ui/tasks.md +22 -0
- spinagram-0.1.0rc1/.specs/features/png-to-svg/design.md +38 -0
- spinagram-0.1.0rc1/.specs/features/png-to-svg/spec.md +55 -0
- spinagram-0.1.0rc1/.specs/features/png-to-svg/tasks.md +26 -0
- spinagram-0.1.0rc1/.specs/features/pptx-svg/spec.md +44 -0
- spinagram-0.1.0rc1/.specs/features/selfhosted-icons/spec.md +142 -0
- spinagram-0.1.0rc1/.specs/features/selfhosted-icons/tasks.md +312 -0
- spinagram-0.1.0rc1/.specs/features/selfhosted-icons/validation.md +225 -0
- spinagram-0.1.0rc1/.specs/lessons.json +405 -0
- spinagram-0.1.0rc1/.specs/project/PROJECT.md +25 -0
- spinagram-0.1.0rc1/.specs/project/STATE.md +84 -0
- spinagram-0.1.0rc1/CHANGELOG.md +151 -0
- spinagram-0.1.0rc1/LICENSE +21 -0
- spinagram-0.1.0rc1/PKG-INFO +225 -0
- spinagram-0.1.0rc1/README.md +175 -0
- spinagram-0.1.0rc1/cliff.toml +55 -0
- spinagram-0.1.0rc1/docs/.gitignore +26 -0
- spinagram-0.1.0rc1/docs/app/(home)/layout.tsx +6 -0
- spinagram-0.1.0rc1/docs/app/(home)/page.tsx +67 -0
- spinagram-0.1.0rc1/docs/app/api/search/route.ts +9 -0
- spinagram-0.1.0rc1/docs/app/docs/[[...slug]]/page.tsx +60 -0
- spinagram-0.1.0rc1/docs/app/docs/layout.tsx +11 -0
- spinagram-0.1.0rc1/docs/app/global.css +12 -0
- spinagram-0.1.0rc1/docs/app/layout.tsx +23 -0
- spinagram-0.1.0rc1/docs/app/llms-full.txt/route.ts +7 -0
- spinagram-0.1.0rc1/docs/app/llms.mdx/docs/[[...slug]]/route.ts +24 -0
- spinagram-0.1.0rc1/docs/app/llms.txt/route.ts +7 -0
- spinagram-0.1.0rc1/docs/bun.lock +939 -0
- spinagram-0.1.0rc1/docs/components/mdx.tsx +21 -0
- spinagram-0.1.0rc1/docs/components/provider.tsx +8 -0
- spinagram-0.1.0rc1/docs/components/search.tsx +38 -0
- spinagram-0.1.0rc1/docs/content/docs/ai-skills.mdx +72 -0
- spinagram-0.1.0rc1/docs/content/docs/annotations.mdx +169 -0
- spinagram-0.1.0rc1/docs/content/docs/auto-layout.mdx +92 -0
- spinagram-0.1.0rc1/docs/content/docs/behavior.mdx +62 -0
- spinagram-0.1.0rc1/docs/content/docs/cli.mdx +48 -0
- spinagram-0.1.0rc1/docs/content/docs/css-reference.mdx +168 -0
- spinagram-0.1.0rc1/docs/content/docs/development.mdx +67 -0
- spinagram-0.1.0rc1/docs/content/docs/extending.mdx +40 -0
- spinagram-0.1.0rc1/docs/content/docs/facts.mdx +25 -0
- spinagram-0.1.0rc1/docs/content/docs/figma.mdx +33 -0
- spinagram-0.1.0rc1/docs/content/docs/getting-started.mdx +84 -0
- spinagram-0.1.0rc1/docs/content/docs/icons.mdx +102 -0
- spinagram-0.1.0rc1/docs/content/docs/index.mdx +44 -0
- spinagram-0.1.0rc1/docs/content/docs/live-ui.mdx +38 -0
- spinagram-0.1.0rc1/docs/content/docs/manual-layout.mdx +87 -0
- spinagram-0.1.0rc1/docs/content/docs/meta.json +30 -0
- spinagram-0.1.0rc1/docs/content/docs/outputs.mdx +28 -0
- spinagram-0.1.0rc1/docs/content/docs/python.mdx +191 -0
- spinagram-0.1.0rc1/docs/content/docs/styling.mdx +58 -0
- spinagram-0.1.0rc1/docs/content/docs/themes.mdx +29 -0
- spinagram-0.1.0rc1/docs/content/docs/yaml.mdx +45 -0
- spinagram-0.1.0rc1/docs/diagrams/overview.yaml +24 -0
- spinagram-0.1.0rc1/docs/lib/cn.ts +1 -0
- spinagram-0.1.0rc1/docs/lib/layout.shared.tsx +18 -0
- spinagram-0.1.0rc1/docs/lib/shared.ts +20 -0
- spinagram-0.1.0rc1/docs/lib/source.ts +30 -0
- spinagram-0.1.0rc1/docs/next.config.mjs +21 -0
- spinagram-0.1.0rc1/docs/package.json +32 -0
- spinagram-0.1.0rc1/docs/postcss.config.mjs +7 -0
- spinagram-0.1.0rc1/docs/public/assets/css/g01.svg +1 -0
- spinagram-0.1.0rc1/docs/public/assets/css/g02.svg +1 -0
- spinagram-0.1.0rc1/docs/public/assets/css/g03.svg +1 -0
- spinagram-0.1.0rc1/docs/public/assets/css/g04.svg +1 -0
- spinagram-0.1.0rc1/docs/public/assets/css/g05.svg +1 -0
- spinagram-0.1.0rc1/docs/public/assets/css/g06.svg +1 -0
- spinagram-0.1.0rc1/docs/public/assets/css/g07.svg +1 -0
- spinagram-0.1.0rc1/docs/public/assets/css/g08.svg +1 -0
- spinagram-0.1.0rc1/docs/public/assets/css/inspector.webp +0 -0
- spinagram-0.1.0rc1/docs/public/assets/demo/b01.svg +1 -0
- spinagram-0.1.0rc1/docs/public/assets/demo/b02.svg +1 -0
- spinagram-0.1.0rc1/docs/public/assets/demo/b03.svg +1 -0
- spinagram-0.1.0rc1/docs/public/assets/demo/b04.svg +1 -0
- spinagram-0.1.0rc1/docs/public/assets/demo/c01.svg +1 -0
- spinagram-0.1.0rc1/docs/public/assets/logo.svg +19 -0
- spinagram-0.1.0rc1/docs/public/assets/overview.svg +1 -0
- spinagram-0.1.0rc1/docs/public/assets/quickstart.svg +1 -0
- spinagram-0.1.0rc1/docs/scripts/css_gallery.py +102 -0
- spinagram-0.1.0rc1/docs/tsconfig.json +34 -0
- spinagram-0.1.0rc1/examples/aws_layout.yaml +37 -0
- spinagram-0.1.0rc1/examples/behavior/activity.py +27 -0
- spinagram-0.1.0rc1/examples/behavior/flowchart.py +23 -0
- spinagram-0.1.0rc1/examples/behavior/mixed.py +24 -0
- spinagram-0.1.0rc1/examples/behavior/state_machine.py +31 -0
- spinagram-0.1.0rc1/examples/decision.yaml +24 -0
- spinagram-0.1.0rc1/examples/packs/multicloud.yaml +19 -0
- spinagram-0.1.0rc1/examples/quickstart.py +24 -0
- spinagram-0.1.0rc1/examples/quickstart.yaml +31 -0
- spinagram-0.1.0rc1/examples/release_flow.py +24 -0
- spinagram-0.1.0rc1/examples/showcase/aws.css +46 -0
- spinagram-0.1.0rc1/examples/showcase/aws_network_hub.py +51 -0
- spinagram-0.1.0rc1/examples/showcase/aws_three_tier.py +53 -0
- spinagram-0.1.0rc1/examples/showcase/campus.css +19 -0
- spinagram-0.1.0rc1/examples/showcase/campus_network.py +45 -0
- spinagram-0.1.0rc1/examples/showcase/evpn_fabric.py +68 -0
- spinagram-0.1.0rc1/examples/showcase/fabric.css +27 -0
- spinagram-0.1.0rc1/examples/showcase/gcp.css +34 -0
- spinagram-0.1.0rc1/examples/showcase/gcp_global_app.py +49 -0
- spinagram-0.1.0rc1/examples/showcase/homelab.css +26 -0
- spinagram-0.1.0rc1/examples/showcase/homelab.py +49 -0
- spinagram-0.1.0rc1/examples/showcase/k8s.css +48 -0
- spinagram-0.1.0rc1/examples/showcase/k8s_networking.py +50 -0
- spinagram-0.1.0rc1/examples/showcase/k8s_platform.py +52 -0
- spinagram-0.1.0rc1/examples/showcase/notes.css +9 -0
- spinagram-0.1.0rc1/examples/showcase/notes.py +23 -0
- spinagram-0.1.0rc1/examples/showcase/showcase.css +17 -0
- spinagram-0.1.0rc1/examples/software_architecture.yaml +25 -0
- spinagram-0.1.0rc1/examples/styling/brand.css +18 -0
- spinagram-0.1.0rc1/examples/styling/gallery/01-boxes.css +4 -0
- spinagram-0.1.0rc1/examples/styling/gallery/02-dashes.css +5 -0
- spinagram-0.1.0rc1/examples/styling/gallery/03-shadows.css +4 -0
- spinagram-0.1.0rc1/examples/styling/gallery/04-edges.css +8 -0
- spinagram-0.1.0rc1/examples/styling/gallery/05-text.css +6 -0
- spinagram-0.1.0rc1/examples/styling/gallery/06-tokens.css +5 -0
- spinagram-0.1.0rc1/examples/styling/gallery/07-selectors.css +7 -0
- spinagram-0.1.0rc1/examples/styling/gallery/08-behavior.css +6 -0
- spinagram-0.1.0rc1/examples/styling/gallery/gallery.py +85 -0
- spinagram-0.1.0rc1/examples/styling/styled.py +19 -0
- spinagram-0.1.0rc1/pyproject.toml +84 -0
- spinagram-0.1.0rc1/src/spinagram/__init__.py +11 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram/SKILL.md +244 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/SKILL.md +155 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/aws.css +46 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/aws_network_hub.py +51 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/aws_three_tier.py +53 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/campus.css +19 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/campus_network.py +45 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/evpn_fabric.py +68 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/fabric.css +27 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/gcp.css +34 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/gcp_global_app.py +49 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/homelab.css +26 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/homelab.py +49 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/k8s.css +48 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/k8s_networking.py +50 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/k8s_platform.py +52 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/notes.css +9 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/notes.py +23 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/examples/showcase.css +17 -0
- spinagram-0.1.0rc1/src/spinagram/agent_skills/spinagram-css/references/recipes.css +72 -0
- spinagram-0.1.0rc1/src/spinagram/annotate.py +539 -0
- spinagram-0.1.0rc1/src/spinagram/api.py +81 -0
- spinagram-0.1.0rc1/src/spinagram/autolayout/__init__.py +442 -0
- spinagram-0.1.0rc1/src/spinagram/autolayout/engines/__init__.py +54 -0
- spinagram-0.1.0rc1/src/spinagram/autolayout/engines/d2.py +296 -0
- spinagram-0.1.0rc1/src/spinagram/autolayout/engines/dot.py +179 -0
- spinagram-0.1.0rc1/src/spinagram/autolayout/engines/elk.py +170 -0
- spinagram-0.1.0rc1/src/spinagram/autolayout/engines/elk_runner.mjs +35 -0
- spinagram-0.1.0rc1/src/spinagram/autolayout/engines/flex.py +97 -0
- spinagram-0.1.0rc1/src/spinagram/autolayout/engines/layered.py +679 -0
- spinagram-0.1.0rc1/src/spinagram/autolayout/graph.py +60 -0
- spinagram-0.1.0rc1/src/spinagram/autolayout/route.py +146 -0
- spinagram-0.1.0rc1/src/spinagram/autolayout/sizing.py +77 -0
- spinagram-0.1.0rc1/src/spinagram/backends/__init__.py +48 -0
- spinagram-0.1.0rc1/src/spinagram/backends/figma.py +589 -0
- spinagram-0.1.0rc1/src/spinagram/backends/png.py +40 -0
- spinagram-0.1.0rc1/src/spinagram/backends/pptx.py +555 -0
- spinagram-0.1.0rc1/src/spinagram/backends/svg.py +257 -0
- spinagram-0.1.0rc1/src/spinagram/behavior.py +311 -0
- spinagram-0.1.0rc1/src/spinagram/cli.py +344 -0
- spinagram-0.1.0rc1/src/spinagram/css.py +706 -0
- spinagram-0.1.0rc1/src/spinagram/deck.py +241 -0
- spinagram-0.1.0rc1/src/spinagram/facts.py +195 -0
- spinagram-0.1.0rc1/src/spinagram/icon_packs/aws.yaml +43 -0
- spinagram-0.1.0rc1/src/spinagram/icon_packs/azure.yaml +12 -0
- spinagram-0.1.0rc1/src/spinagram/icon_packs/gcp.yaml +11 -0
- spinagram-0.1.0rc1/src/spinagram/icon_packs/k8s.yaml +9 -0
- spinagram-0.1.0rc1/src/spinagram/icon_packs/network.yaml +30 -0
- spinagram-0.1.0rc1/src/spinagram/icon_packs/oci.yaml +9 -0
- spinagram-0.1.0rc1/src/spinagram/icon_packs/openstack.yaml +8 -0
- spinagram-0.1.0rc1/src/spinagram/icon_packs/selfhosted.yaml +28 -0
- spinagram-0.1.0rc1/src/spinagram/icon_packs/terrastruct.yaml +84 -0
- spinagram-0.1.0rc1/src/spinagram/icons.py +438 -0
- spinagram-0.1.0rc1/src/spinagram/inspect_fmt.py +193 -0
- spinagram-0.1.0rc1/src/spinagram/layout.py +501 -0
- spinagram-0.1.0rc1/src/spinagram/metadata.py +66 -0
- spinagram-0.1.0rc1/src/spinagram/model.py +604 -0
- spinagram-0.1.0rc1/src/spinagram/qa.py +73 -0
- spinagram-0.1.0rc1/src/spinagram/scene.py +239 -0
- spinagram-0.1.0rc1/src/spinagram/shapes.py +248 -0
- spinagram-0.1.0rc1/src/spinagram/skills.py +99 -0
- spinagram-0.1.0rc1/src/spinagram/text.py +83 -0
- spinagram-0.1.0rc1/src/spinagram/theme.py +291 -0
- spinagram-0.1.0rc1/src/spinagram/themes/default/theme.yaml +114 -0
- spinagram-0.1.0rc1/src/spinagram/ui/__init__.py +5 -0
- spinagram-0.1.0rc1/src/spinagram/ui/catalog.py +118 -0
- spinagram-0.1.0rc1/src/spinagram/ui/events.py +48 -0
- spinagram-0.1.0rc1/src/spinagram/ui/server.py +271 -0
- spinagram-0.1.0rc1/src/spinagram/ui/static/assets/index-BDoYKVM5.css +2 -0
- spinagram-0.1.0rc1/src/spinagram/ui/static/assets/index-DAg_fACF.js +11 -0
- spinagram-0.1.0rc1/src/spinagram/ui/static/index.html +13 -0
- spinagram-0.1.0rc1/src/spinagram/ui/themes.py +152 -0
- spinagram-0.1.0rc1/src/spinagram/ui/workspace.py +340 -0
- spinagram-0.1.0rc1/src/spinagram/vectorize.py +158 -0
- spinagram-0.1.0rc1/src/spinagram/yaml_io.py +322 -0
- spinagram-0.1.0rc1/tests/js/figma_mock.mjs +43 -0
- spinagram-0.1.0rc1/tests/test_annotations.py +958 -0
- spinagram-0.1.0rc1/tests/test_autolayout.py +223 -0
- spinagram-0.1.0rc1/tests/test_behavior.py +728 -0
- spinagram-0.1.0rc1/tests/test_css.py +642 -0
- spinagram-0.1.0rc1/tests/test_css_inspector.py +387 -0
- spinagram-0.1.0rc1/tests/test_engine.py +517 -0
- spinagram-0.1.0rc1/tests/test_icon_packs.py +179 -0
- spinagram-0.1.0rc1/tests/test_layered.py +542 -0
- spinagram-0.1.0rc1/tests/test_legend_css.py +269 -0
- spinagram-0.1.0rc1/tests/test_metadata.py +276 -0
- spinagram-0.1.0rc1/tests/test_pptx_svg.py +135 -0
- spinagram-0.1.0rc1/tests/test_skills.py +87 -0
- spinagram-0.1.0rc1/tests/test_ui.py +427 -0
- spinagram-0.1.0rc1/tests/test_web_unit.py +16 -0
- spinagram-0.1.0rc1/uv.lock +944 -0
- spinagram-0.1.0rc1/web/.gitignore +2 -0
- spinagram-0.1.0rc1/web/bun.lock +252 -0
- spinagram-0.1.0rc1/web/index.html +12 -0
- spinagram-0.1.0rc1/web/package.json +24 -0
- spinagram-0.1.0rc1/web/src/App.tsx +254 -0
- spinagram-0.1.0rc1/web/src/CatalogShell.tsx +66 -0
- spinagram-0.1.0rc1/web/src/DiagramView.tsx +235 -0
- spinagram-0.1.0rc1/web/src/DiagramsPage.tsx +151 -0
- spinagram-0.1.0rc1/web/src/Inspector.tsx +346 -0
- spinagram-0.1.0rc1/web/src/PackView.tsx +148 -0
- spinagram-0.1.0rc1/web/src/PacksPage.tsx +91 -0
- spinagram-0.1.0rc1/web/src/ThemeView.tsx +193 -0
- spinagram-0.1.0rc1/web/src/ThemesPage.tsx +64 -0
- spinagram-0.1.0rc1/web/src/api.ts +173 -0
- spinagram-0.1.0rc1/web/src/catalog.test.ts +136 -0
- spinagram-0.1.0rc1/web/src/catalog.ts +134 -0
- spinagram-0.1.0rc1/web/src/hooks.test.ts +30 -0
- spinagram-0.1.0rc1/web/src/hooks.ts +170 -0
- spinagram-0.1.0rc1/web/src/index.css +24 -0
- spinagram-0.1.0rc1/web/src/main.tsx +10 -0
- spinagram-0.1.0rc1/web/src/previews.test.ts +91 -0
- spinagram-0.1.0rc1/web/tsconfig.json +18 -0
- spinagram-0.1.0rc1/web/vite.config.ts +12 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_call:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
15
|
+
|
|
16
|
+
env:
|
|
17
|
+
UV_VERSION: "0.10.4"
|
|
18
|
+
BUN_VERSION: "1.4.2"
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
web:
|
|
22
|
+
name: UI bundle (bun)
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v5
|
|
26
|
+
- uses: oven-sh/setup-bun@v2
|
|
27
|
+
with:
|
|
28
|
+
bun-version: ${{ env.BUN_VERSION }}
|
|
29
|
+
- run: bun install --frozen-lockfile
|
|
30
|
+
working-directory: web
|
|
31
|
+
- run: bun run build # tsc -b && vite build -> src/spinagram/ui/static
|
|
32
|
+
working-directory: web
|
|
33
|
+
- name: Committed bundle matches web/
|
|
34
|
+
run: |
|
|
35
|
+
if [ -n "$(git status --porcelain -- src/spinagram/ui/static)" ]; then
|
|
36
|
+
git status --short -- src/spinagram/ui/static
|
|
37
|
+
echo "::error::src/spinagram/ui/static is stale: run 'bun run build' in web/ and commit it"
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
lint:
|
|
42
|
+
name: Lint (ruff)
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/checkout@v5
|
|
46
|
+
- uses: astral-sh/setup-uv@v6
|
|
47
|
+
with:
|
|
48
|
+
version: ${{ env.UV_VERSION }}
|
|
49
|
+
enable-cache: true
|
|
50
|
+
- run: uv run --locked --only-group dev ruff check src tests examples
|
|
51
|
+
|
|
52
|
+
test:
|
|
53
|
+
name: Tests (Python ${{ matrix.python }})
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
strategy:
|
|
56
|
+
fail-fast: false
|
|
57
|
+
matrix:
|
|
58
|
+
python: ["3.11", "3.12", "3.13"]
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/checkout@v5
|
|
61
|
+
- uses: astral-sh/setup-uv@v6
|
|
62
|
+
with:
|
|
63
|
+
version: ${{ env.UV_VERSION }}
|
|
64
|
+
python-version: ${{ matrix.python }}
|
|
65
|
+
enable-cache: true
|
|
66
|
+
- run: uv sync --locked --all-extras
|
|
67
|
+
- run: uv run pytest -q
|
|
68
|
+
|
|
69
|
+
build:
|
|
70
|
+
name: Build sdist + wheel (uv)
|
|
71
|
+
needs: [web, lint, test]
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
steps:
|
|
74
|
+
- uses: actions/checkout@v5
|
|
75
|
+
- uses: astral-sh/setup-uv@v6
|
|
76
|
+
with:
|
|
77
|
+
version: ${{ env.UV_VERSION }}
|
|
78
|
+
- run: uv build
|
|
79
|
+
- name: Wheel ships the UI bundle
|
|
80
|
+
run: unzip -l dist/*.whl | grep -q 'spinagram/ui/static/index.html'
|
|
81
|
+
- name: Smoke test the built wheel
|
|
82
|
+
run: |
|
|
83
|
+
uv run --isolated --no-project --with "$(ls dist/*.whl)[ui]" spinagram --help
|
|
84
|
+
uv run --isolated --no-project --with "$(ls dist/*.whl)" spinagram render examples/decision.yaml -f svg -o /tmp/out
|
|
85
|
+
test -n "$(ls /tmp/out/*.svg)"
|
|
86
|
+
- uses: actions/upload-artifact@v4
|
|
87
|
+
with:
|
|
88
|
+
name: dist
|
|
89
|
+
path: dist/
|
|
90
|
+
if-no-files-found: error
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: docs-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
build:
|
|
18
|
+
name: Build site (Fumadocs)
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
defaults:
|
|
21
|
+
run:
|
|
22
|
+
working-directory: docs
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v5
|
|
25
|
+
- uses: oven-sh/setup-bun@v2
|
|
26
|
+
with:
|
|
27
|
+
bun-version: "1.4.2"
|
|
28
|
+
- run: bun install --frozen-lockfile
|
|
29
|
+
- name: Static export with the search index
|
|
30
|
+
run: bun run build
|
|
31
|
+
env:
|
|
32
|
+
NEXT_PUBLIC_BASE_PATH: /spinagram # served at https://dangerousplay.github.io/spinagram/
|
|
33
|
+
- uses: actions/upload-pages-artifact@v3
|
|
34
|
+
with:
|
|
35
|
+
path: docs/out/
|
|
36
|
+
|
|
37
|
+
deploy:
|
|
38
|
+
name: Deploy to GitHub Pages
|
|
39
|
+
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
|
40
|
+
needs: build
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
permissions:
|
|
43
|
+
pages: write
|
|
44
|
+
id-token: write
|
|
45
|
+
environment:
|
|
46
|
+
name: github-pages
|
|
47
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
48
|
+
steps:
|
|
49
|
+
- id: deployment
|
|
50
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Push a tag that matches the version in pyproject.toml, with the changelog regenerated for it:
|
|
4
|
+
# uv version --bump minor
|
|
5
|
+
# uvx git-cliff --tag "v$(uv version --short)" -o CHANGELOG.md
|
|
6
|
+
# git commit -am "chore(release): v$(uv version --short)"
|
|
7
|
+
# git tag "v$(uv version --short)" && git push --follow-tags
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
tags: ["v*"]
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
ci:
|
|
17
|
+
uses: ./.github/workflows/ci.yml
|
|
18
|
+
|
|
19
|
+
publish:
|
|
20
|
+
name: Publish to PyPI
|
|
21
|
+
needs: ci
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
environment:
|
|
24
|
+
name: pypi
|
|
25
|
+
url: https://pypi.org/project/spinagram/
|
|
26
|
+
permissions:
|
|
27
|
+
id-token: write # PyPI trusted publishing (OIDC), no API token stored
|
|
28
|
+
contents: write # GitHub release
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v5
|
|
31
|
+
with:
|
|
32
|
+
fetch-depth: 0 # git-cliff reads the whole history and the tags
|
|
33
|
+
- uses: astral-sh/setup-uv@v6
|
|
34
|
+
with:
|
|
35
|
+
version: "0.10.4"
|
|
36
|
+
- name: Tag matches project version
|
|
37
|
+
run: |
|
|
38
|
+
v="$(uv version --short)"
|
|
39
|
+
if [ "v$v" != "$GITHUB_REF_NAME" ]; then
|
|
40
|
+
echo "::error::tag $GITHUB_REF_NAME does not match pyproject version $v"
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
- uses: actions/download-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: dist
|
|
46
|
+
path: dist/
|
|
47
|
+
- run: uv publish --trusted-publishing always
|
|
48
|
+
- name: Release notes (git-cliff)
|
|
49
|
+
run: uvx git-cliff@2.14.2 --latest --strip header -o RELEASE_NOTES.md
|
|
50
|
+
- name: GitHub release
|
|
51
|
+
env:
|
|
52
|
+
GH_TOKEN: ${{ github.token }}
|
|
53
|
+
run: |
|
|
54
|
+
pre=""
|
|
55
|
+
case "$GITHUB_REF_NAME" in *a[0-9]*|*b[0-9]*|*rc[0-9]*|*.dev[0-9]*) pre="--prerelease" ;; esac
|
|
56
|
+
gh release create "$GITHUB_REF_NAME" dist/* --title "$GITHUB_REF_NAME" --notes-file RELEASE_NOTES.md $pre
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# LESSONS - auto-maintained by scripts/lessons.py
|
|
2
|
+
|
|
3
|
+
> Machine-owned. Do NOT hand-edit. Changes are overwritten on the next `lessons.py` write.
|
|
4
|
+
> Canonical state lives in `.specs/lessons.json`. Edit lessons only via the script.
|
|
5
|
+
> promote_threshold=2 distinct features · window_days=45 · quarantine_threshold=2
|
|
6
|
+
|
|
7
|
+
## Confirmed (load these at Specify/Design)
|
|
8
|
+
|
|
9
|
+
Corroborated across multiple features. Safe to apply as guidance.
|
|
10
|
+
|
|
11
|
+
_none_
|
|
12
|
+
|
|
13
|
+
## Candidates (under observation - do NOT load as guidance yet)
|
|
14
|
+
|
|
15
|
+
Seen once or not yet corroborated. Tracked, not trusted.
|
|
16
|
+
|
|
17
|
+
### L-001 - Pin determinism with a golden output hash checked across many PYTHONHASHSEED values, not a few in-process runs.
|
|
18
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `tests` · harmful: 0
|
|
19
|
+
- features: layered-layout
|
|
20
|
+
- evidence: M9b (hash tie-break) (tests)
|
|
21
|
+
- last seen: 2026-10-12T04:39:39Z
|
|
22
|
+
|
|
23
|
+
### L-002 - Give placement ACs a measurable bound (e.g. pill within 6 px of its segment) before writing the test.
|
|
24
|
+
- signal: `spec_precision_gap` · recurrence: 1 feature(s) · scope: `spec` · harmful: 0
|
|
25
|
+
- features: layered-layout
|
|
26
|
+
- evidence: LAY-12 (spec)
|
|
27
|
+
- last seen: 2026-10-12T04:39:39Z
|
|
28
|
+
|
|
29
|
+
### L-003 - Guard output-unchanged-without-the-feature with a golden render captured before the feature, not an identity check.
|
|
30
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `tests` · harmful: 0
|
|
31
|
+
- features: css-styling
|
|
32
|
+
- evidence: M15 (CSS-27) (tests)
|
|
33
|
+
- last seen: 2026-10-12T04:39:39Z
|
|
34
|
+
|
|
35
|
+
### L-004 - When an AC describes geometry that depends on layout (bars spanning branches), test the laid-out result, not the default size.
|
|
36
|
+
- signal: `ac_gap` · recurrence: 1 feature(s) · scope: `tests` · harmful: 0
|
|
37
|
+
- features: behavior-diagrams
|
|
38
|
+
- evidence: BHV-02 / G1 (tests)
|
|
39
|
+
- last seen: 2026-10-12T04:39:39Z
|
|
40
|
+
|
|
41
|
+
### L-005 - Never pipe the test command into tail before chaining a commit; check pytest's own exit code.
|
|
42
|
+
- signal: `gate_fail` · recurrence: 1 feature(s) · scope: `process` · harmful: 0
|
|
43
|
+
- features: layered-layout
|
|
44
|
+
- evidence: 99549c5 (process)
|
|
45
|
+
- last seen: 2026-10-12T04:39:39Z
|
|
46
|
+
|
|
47
|
+
### L-006 - Parse value formats with a full match; lenient prefix parsing hides upstream bugs from tests.
|
|
48
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `src` · harmful: 0
|
|
49
|
+
- features: css-styling
|
|
50
|
+
- evidence: F9 / R3 (CSS-16) (src)
|
|
51
|
+
- last seen: 2026-10-12T04:41:36Z
|
|
52
|
+
|
|
53
|
+
### L-007 - Check optional spec keys with is-None, not truthiness, so falsy wrong-typed values like 0, false or {} still fail validation
|
|
54
|
+
- signal: `ac_gap` · recurrence: 1 feature(s) · scope: `yaml-loaders` · harmful: 0
|
|
55
|
+
- features: diagram-catalog
|
|
56
|
+
- evidence: META-03 src/spinagram/yaml_io.py:55 (yaml-loaders)
|
|
57
|
+
- last seen: 2026-10-12T17:25:04Z
|
|
58
|
+
|
|
59
|
+
### L-008 - Keep every data transformation an AC names (sorting, filtering, sampling) in the unit-tested pure-logic module, not inline in a component
|
|
60
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `web-ui` · harmful: 0
|
|
61
|
+
- features: catalog-previews
|
|
62
|
+
- evidence: M16 web/src/PacksPage.tsx:45 (PREV-07) (web-ui)
|
|
63
|
+
- last seen: 2026-10-12T17:38:46Z
|
|
64
|
+
|
|
65
|
+
### L-009 - When an AC requires an error response to carry the error message, assert the message text, not just that an error field is present
|
|
66
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `routes` · harmful: 0
|
|
67
|
+
- features: catalog-previews
|
|
68
|
+
- evidence: M17 tests/test_ui.py:161 (PREV-03) (routes)
|
|
69
|
+
- last seen: 2026-10-12T17:38:46Z
|
|
70
|
+
|
|
71
|
+
### L-010 - Assert that styled values reach the rendered scene shapes, not only the computed style map
|
|
72
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `css` · harmful: 0
|
|
73
|
+
- features: annotations
|
|
74
|
+
- evidence: validation.md M35/M36 src/spinagram/annotate.py:207,228 (css)
|
|
75
|
+
- last seen: 2026-10-12T18:18:23Z
|
|
76
|
+
|
|
77
|
+
### L-011 - Derive expected geometry independently instead of reusing the function under test
|
|
78
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `tests` · harmful: 0
|
|
79
|
+
- features: annotations
|
|
80
|
+
- evidence: validation.md M07 tests/test_annotations.py:330 (tests)
|
|
81
|
+
- last seen: 2026-10-12T18:18:23Z
|
|
82
|
+
|
|
83
|
+
### L-012 - Assert the exact numeric value in diagnostic messages, not just the message prefix
|
|
84
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `qa` · harmful: 0
|
|
85
|
+
- features: annotations
|
|
86
|
+
- evidence: validation.md M37 tests/test_annotations.py:726 (qa)
|
|
87
|
+
- last seen: 2026-10-12T18:18:23Z
|
|
88
|
+
|
|
89
|
+
### L-013 - Cover first-match rules with a fixture that has at least two candidates
|
|
90
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `tests` · harmful: 0
|
|
91
|
+
- features: annotations
|
|
92
|
+
- evidence: validation.md M38 src/spinagram/model.py:492 (tests)
|
|
93
|
+
- last seen: 2026-10-12T18:18:23Z
|
|
94
|
+
|
|
95
|
+
### L-014 - Give every clause of a multi-clause acceptance criterion its own assertion
|
|
96
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `tests` · harmful: 0
|
|
97
|
+
- features: annotations
|
|
98
|
+
- evidence: validation.md M21/M39/M40 ANN-03 ANN-10 ANN-22 (tests)
|
|
99
|
+
- last seen: 2026-10-12T18:18:23Z
|
|
100
|
+
|
|
101
|
+
### L-015 - When a spec says first-use order over mixed element types, state the order across types
|
|
102
|
+
- signal: `spec_precision_gap` · recurrence: 1 feature(s) · scope: `spec` · harmful: 0
|
|
103
|
+
- features: annotations
|
|
104
|
+
- evidence: validation.md ANN-01 src/spinagram/annotate.py:84 (spec)
|
|
105
|
+
- last seen: 2026-10-12T18:18:23Z
|
|
106
|
+
|
|
107
|
+
### L-016 - Copy fixture paths from the real upstream source so every naming form it actually uses, including optional-group branches, is exercised
|
|
108
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `tests` · harmful: 0
|
|
109
|
+
- features: selfhosted-icons
|
|
110
|
+
- evidence: validation.md M02 src/spinagram/icon_packs/network.yaml:13 tests/test_icon_packs.py:51 (ICON-09) (tests)
|
|
111
|
+
- last seen: 2026-10-12T19:04:51Z
|
|
112
|
+
|
|
113
|
+
### L-017 - Test each layout property on every band side or branch it applies to, not only the default side
|
|
114
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `tests` · harmful: 0
|
|
115
|
+
- features: legend-css
|
|
116
|
+
- evidence: validation.md M12/M13 src/spinagram/annotate.py:187,370 (LCSS-08) (tests)
|
|
117
|
+
- last seen: 2026-10-12T20:03:34Z
|
|
118
|
+
|
|
119
|
+
### L-018 - When an explicit setting overrides an automatic rule, test it under the condition that triggers the rule
|
|
120
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `tests` · harmful: 0
|
|
121
|
+
- features: legend-css
|
|
122
|
+
- evidence: validation.md M15 src/spinagram/annotate.py:354 (LCSS-08) (tests)
|
|
123
|
+
- last seen: 2026-10-12T20:03:34Z
|
|
124
|
+
|
|
125
|
+
### L-019 - When a default moves from code to the stylesheet, test that every loader path, including YAML, leaves the value unset
|
|
126
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `yaml-loaders` · harmful: 0
|
|
127
|
+
- features: legend-css
|
|
128
|
+
- evidence: validation.md M30 src/spinagram/yaml_io.py:140 (LCSS-04) (yaml-loaders)
|
|
129
|
+
- last seen: 2026-10-12T20:03:34Z
|
|
130
|
+
|
|
131
|
+
### L-020 - When new enum values are added, cover each one in every consumer that branches on it, including qa checks
|
|
132
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `tests` · harmful: 0
|
|
133
|
+
- features: legend-css
|
|
134
|
+
- evidence: validation.md M20/M21 src/spinagram/annotate.py:525-526 (ANN-12 for left/top) (tests)
|
|
135
|
+
- last seen: 2026-10-12T20:03:34Z
|
|
136
|
+
|
|
137
|
+
### L-021 - Give every clause of a multi-clause acceptance criterion its own assertion
|
|
138
|
+
- signal: `ac_gap` · recurrence: 1 feature(s) · scope: `tests` · harmful: 0
|
|
139
|
+
- features: legend-css
|
|
140
|
+
- evidence: validation.md LCSS-05 clause 2 tests/test_legend_css.py:42 (tests)
|
|
141
|
+
- last seen: 2026-10-12T20:03:34Z
|
|
142
|
+
|
|
143
|
+
### L-022 - When one function reserves space and another draws into it, assert both against the same independently derived size
|
|
144
|
+
- signal: `surviving_mutant` · recurrence: 1 feature(s) · scope: `tests` · harmful: 0
|
|
145
|
+
- features: legend-css
|
|
146
|
+
- evidence: validation.md iter2 M12 src/spinagram/annotate.py:187 (LCSS-08) (tests)
|
|
147
|
+
- last seen: 2026-10-12T20:09:01Z
|
|
148
|
+
|
|
149
|
+
## Quarantined (failed when applied - ignore)
|
|
150
|
+
|
|
151
|
+
A confirmed lesson that recurred alongside failure. Kept for the maintainer to review.
|
|
152
|
+
|
|
153
|
+
_none_
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Annotations Design
|
|
2
|
+
|
|
3
|
+
**Spec**: `.specs/features/annotations/spec.md`
|
|
4
|
+
**Status**: Approved
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Architecture Overview
|
|
9
|
+
|
|
10
|
+
Annotations are model data (`Diagram.annotations`) turned into ordinary scene primitives by one new module,
|
|
11
|
+
`spinagram/annotate.py`. Because the scene already renders natively in SVG, PNG, PowerPoint and Figma (rect and
|
|
12
|
+
pill shapes with labels, icons and shadows; connectors with markers; text), every annotation is native in every
|
|
13
|
+
backend without backend changes. CSS reaches annotations through new elements in the style tree.
|
|
14
|
+
|
|
15
|
+
```mermaid
|
|
16
|
+
graph TD
|
|
17
|
+
API[Python: add_legend / step / callout / note variant] --> M[model.Annotations]
|
|
18
|
+
YAML[YAML: annotations:, kind: note + variant] --> M
|
|
19
|
+
M --> CSS[css.tree: legend, step, callout elements]
|
|
20
|
+
M --> BAND[annotate.band: side + size]
|
|
21
|
+
BAND --> AREA[autolayout._area minus band]
|
|
22
|
+
AREA --> LAYOUT[engines place content]
|
|
23
|
+
LAYOUT --> B[layout._Builder.build]
|
|
24
|
+
B --> ANN[annotate.Annotator: panels, badges, callouts]
|
|
25
|
+
CSS --> ANN
|
|
26
|
+
ANN --> SCENE[Shapes, Connectors, Text in the scene]
|
|
27
|
+
SCENE --> SVG & PPTX & FIGMA
|
|
28
|
+
ANN --> QA[qa.check: legend unused, steps duplicate, band overflow/overlap, callout overlap]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Order inside `build`: zones → nodes → tables → edges → **step badges** → **panels** → **callouts** → chrome.
|
|
32
|
+
Callouts come last among content so they are placed against every other shape.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Code Reuse Analysis
|
|
37
|
+
|
|
38
|
+
| Component | Location | How to Use |
|
|
39
|
+
| --------- | -------- | ---------- |
|
|
40
|
+
| `_Builder.pill`, `_decorate`, `_css`, `_text` | `src/spinagram/layout.py:58-96` | Badges are pills (w = h → circle); panels and bubbles are rect shapes decorated with computed CSS |
|
|
41
|
+
| `Connector` with markers | `src/spinagram/scene.py:110` | Legend edge samples, callout leader lines (`marker_end="circle"`) |
|
|
42
|
+
| `Label`/`TextBlock`/`wrap_paragraphs` | `src/spinagram/scene.py`, `text.py` | Panel rows, step texts, bubble text |
|
|
43
|
+
| `_area` | `src/spinagram/autolayout/__init__.py:236` | Subtract the band |
|
|
44
|
+
| `css.tree` / `compute` | `src/spinagram/css.py:521` | Add annotation elements; computed styles under keys `legend`, `step`, `callout:<i>` |
|
|
45
|
+
| `qa.check` / `Issue` | `src/spinagram/qa.py:49` | New checks fed by `scene.annotation_issues` |
|
|
46
|
+
| Node kind `note` | `src/spinagram/model.py:315` | Variants as a node attribute; CSS class added |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Components
|
|
51
|
+
|
|
52
|
+
### model: Annotations
|
|
53
|
+
|
|
54
|
+
- **Location**: `src/spinagram/model.py`
|
|
55
|
+
- **Interfaces**:
|
|
56
|
+
- `LegendSpec(title="Legend", items: dict[str, str] | None, position="right")`
|
|
57
|
+
- `Step(n: int, text: str, edge: int | None, node: str | None)`
|
|
58
|
+
- `Callout(target: str, text: str, title: str | None, kind="info", side="auto", cls=None, css=None)`
|
|
59
|
+
- `Annotations(legend: LegendSpec | None, steps: list[Step], callouts: list[Callout])` as `Diagram.annotations`
|
|
60
|
+
- `Diagram.add_legend(items=None, *, title="Legend", position="right")`
|
|
61
|
+
- `Diagram.step(text, *, on, n=None)`; `on` = `Edge`, `Node`, node id, or `(src, dst)`; `Edge.step(text, n=None)`
|
|
62
|
+
- `Diagram.callout(target, text, *, title=None, kind="info", side="auto", class_=None, css=None)`
|
|
63
|
+
- `Diagram.note(..., variant="sticky")`; validated against `NOTE_VARIANTS`
|
|
64
|
+
|
|
65
|
+
### annotate module
|
|
66
|
+
|
|
67
|
+
- **Location**: `src/spinagram/annotate.py`
|
|
68
|
+
- **Interfaces**:
|
|
69
|
+
- `legend_entries(d) -> list[Entry]` (key, label, kind node|edge|style|icon, element key or None)
|
|
70
|
+
- `band(d, theme) -> tuple[str, float] | None` side and thickness before layout
|
|
71
|
+
- `class Annotator(builder)`: `badges(connectors) -> list[Item]`, `panels() -> list[Item]`, `callouts(obstacles) -> list[Item]`, `issues -> list[tuple[str, str, str]]`
|
|
72
|
+
- **Constants**: `BAND_WIDTH = 360`, `BAND_GAP = 32`, `BADGE = 30`, `CALLOUT_W = 320`, `KINDS = {info: primary, tip: success, warning: warning, risk: alert}`
|
|
73
|
+
|
|
74
|
+
### Theme
|
|
75
|
+
|
|
76
|
+
- **Location**: `src/spinagram/themes/default/theme.yaml`
|
|
77
|
+
- **Adds**: colours `success`, `success-bg`, `success-text`; `defaults.annotations: {band: 360, gap: 32, badge: 30, callout_width: 320, title_size: 20, text_size: 16}`
|
|
78
|
+
|
|
79
|
+
### CSS elements
|
|
80
|
+
|
|
81
|
+
- **Location**: `src/spinagram/css.py`
|
|
82
|
+
- **Adds** under the root: `legend` (key `legend`), `step` (key `step:<n>`), `callout` (classes = kind + `class_`, key `callout:<i>`); note nodes get the variant as a class.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Data Models
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
@dataclass
|
|
90
|
+
class Annotations:
|
|
91
|
+
legend: LegendSpec | None = None
|
|
92
|
+
steps: list[Step] = field(default_factory=list)
|
|
93
|
+
callouts: list[Callout] = field(default_factory=list)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
YAML:
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
annotations:
|
|
100
|
+
legend: {title: Legend, position: right, items: {".security": Security control, "edge.async": Async}}
|
|
101
|
+
steps: [{on: "users -> dns", text: Users resolve the domain}, {on: alb, n: 3, text: ...}]
|
|
102
|
+
callouts: [{on: pvc, text: Snapshots every 6 h, kind: warning, side: auto}]
|
|
103
|
+
nodes:
|
|
104
|
+
- {id: n1, kind: note, variant: card, title: Why, text: ...}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Error Handling Strategy
|
|
110
|
+
|
|
111
|
+
| Error Scenario | Handling | User Impact |
|
|
112
|
+
| -------------- | -------- | ----------- |
|
|
113
|
+
| Unknown callout kind / note variant | `ValueError` naming allowed values | Spec load / build error |
|
|
114
|
+
| Step `on` matches no edge or node | `ValueError` naming the reference | Spec load error |
|
|
115
|
+
| Callout target from another diagram | `ValueError` | Build error |
|
|
116
|
+
| Legend key unused | Drawn from theme default; `check` issue | Warning in check |
|
|
117
|
+
| Panels exceed the band | Drawn clipped to content; `check` overflow | Warning in check |
|
|
118
|
+
| Callout has no free side | Placed on first side; `check` overlap | Warning in check |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Risks & Concerns
|
|
123
|
+
|
|
124
|
+
| Concern | Location (file:line) | Impact | Mitigation |
|
|
125
|
+
| ------- | -------------------- | ------ | ---------- |
|
|
126
|
+
| `_Builder.build` is long and order-sensitive | `src/spinagram/layout.py:304` | New steps could disturb chrome/z-order | Annotations live in `annotate.py`; build only calls three methods at fixed points |
|
|
127
|
+
| `build_scene` is `lru_cache`d on the diagram | `src/spinagram/layout.py:441` | Mutable annotation lists must hash/compare like other fields | Dataclasses with `eq=True` default; covered by existing cache behaviour (diagram is copied in apply_layout) |
|
|
128
|
+
| Manual-layout diagrams have no area to shrink | `src/spinagram/autolayout/__init__.py:236` | Band can overlap content | Spec ANN-12: draw the band at the edge and report overlap |
|
|
129
|
+
| Golden SVG tests for unannotated diagrams | `tests/` | Accidental output change | No annotations → no new items; full suite gates every task |
|
|
130
|
+
| PowerPoint circle badges | `src/spinagram/backends/pptx.py` | Pill with w = h must stay a circle | Integration test inspects the PPTX shape geometry |
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Tech Decisions
|
|
135
|
+
|
|
136
|
+
| Decision | Choice | Rationale |
|
|
137
|
+
| -------- | ------ | --------- |
|
|
138
|
+
| Render path | Compose existing scene primitives | Native in all four backends with no backend code |
|
|
139
|
+
| Module boundary | One `annotate.py` | Keeps `layout.py` from growing further |
|
|
140
|
+
| Placement of callouts | Greedy side order with rectangle overlap tests | Deterministic, explainable, cheap |
|