dr-widget 0.1.5__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 (127) hide show
  1. dr_widget-0.1.5/PKG-INFO +62 -0
  2. dr_widget-0.1.5/README.md +49 -0
  3. dr_widget-0.1.5/pyproject.toml +41 -0
  4. dr_widget-0.1.5/src/dr_widget/__init__.py +5 -0
  5. dr_widget-0.1.5/src/dr_widget/py.typed +0 -0
  6. dr_widget-0.1.5/src/dr_widget/widgets/__init__.py +5 -0
  7. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/.gitignore +24 -0
  8. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/.vscode/extensions.json +3 -0
  9. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/README.md +89 -0
  10. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/__init__.py +283 -0
  11. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/components.json +16 -0
  12. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/index.html +12 -0
  13. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/jsrepo.json +18 -0
  14. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/package.json +49 -0
  15. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/postcss.config.js +6 -0
  16. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/public/fonts/Inter-roman.var.woff2 +0 -0
  17. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/public/vite.svg +1 -0
  18. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/App.svelte +62 -0
  19. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/ConfigFileManager.svelte +605 -0
  20. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/app.css +134 -0
  21. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/index.js +5 -0
  22. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/@test_state.json +20 -0
  23. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/Counter.svelte +10 -0
  24. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/file-drop/BrowseConfigsPanel.svelte +137 -0
  25. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/file-drop/ComplexJsonViewer.svelte +94 -0
  26. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/file-drop/ConfigViewerPanel.svelte +282 -0
  27. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/file-drop/LoadedConfigPreview.svelte +74 -0
  28. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SaveConfigPanel.svelte +449 -0
  29. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SelectedFileRow.svelte +38 -0
  30. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SelectedFilesList.svelte +30 -0
  31. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/file-drop/SimpleJsonViewer.svelte +405 -0
  32. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/badge/badge.svelte +50 -0
  33. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/badge/index.ts +2 -0
  34. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/button/button.svelte +128 -0
  35. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/button/index.ts +27 -0
  36. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-action.svelte +20 -0
  37. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-content.svelte +15 -0
  38. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-description.svelte +20 -0
  39. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-footer.svelte +20 -0
  40. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-header.svelte +23 -0
  41. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card-title.svelte +20 -0
  42. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/card/card.svelte +23 -0
  43. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/card/index.ts +25 -0
  44. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-close.svelte +11 -0
  45. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-content.svelte +47 -0
  46. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-description.svelte +21 -0
  47. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-footer.svelte +24 -0
  48. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-header.svelte +24 -0
  49. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-overlay.svelte +24 -0
  50. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-title.svelte +21 -0
  51. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/dialog-trigger.svelte +11 -0
  52. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/dialog/index.ts +41 -0
  53. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-close.svelte +11 -0
  54. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-content.svelte +41 -0
  55. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-description.svelte +21 -0
  56. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-footer.svelte +24 -0
  57. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-header.svelte +24 -0
  58. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-nested.svelte +16 -0
  59. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-overlay.svelte +24 -0
  60. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-title.svelte +21 -0
  61. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer-trigger.svelte +11 -0
  62. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/drawer.svelte +16 -0
  63. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/drawer/index.ts +45 -0
  64. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-content.svelte +23 -0
  65. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-description.svelte +23 -0
  66. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-header.svelte +20 -0
  67. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-media.svelte +41 -0
  68. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty-title.svelte +20 -0
  69. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/empty.svelte +23 -0
  70. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/empty/index.ts +22 -0
  71. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-content.svelte +20 -0
  72. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-description.svelte +25 -0
  73. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-error.svelte +58 -0
  74. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-group.svelte +23 -0
  75. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-label.svelte +26 -0
  76. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-legend.svelte +29 -0
  77. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-separator.svelte +38 -0
  78. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-set.svelte +24 -0
  79. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field-title.svelte +23 -0
  80. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/field/field.svelte +53 -0
  81. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/field/index.ts +33 -0
  82. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/file-drop-zone/file-drop-zone.svelte +178 -0
  83. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/file-drop-zone/index.ts +29 -0
  84. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/file-drop-zone/types.ts +51 -0
  85. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/item/index.ts +34 -0
  86. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-actions.svelte +20 -0
  87. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-content.svelte +20 -0
  88. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-description.svelte +24 -0
  89. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-footer.svelte +20 -0
  90. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-group.svelte +21 -0
  91. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-header.svelte +20 -0
  92. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-media.svelte +42 -0
  93. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-separator.svelte +19 -0
  94. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item-title.svelte +20 -0
  95. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/item/item.svelte +60 -0
  96. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/label/index.ts +7 -0
  97. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/label/label.svelte +20 -0
  98. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/index.ts +13 -0
  99. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-content.svelte +29 -0
  100. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-description.svelte +20 -0
  101. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-footer.svelte +29 -0
  102. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-header.svelte +29 -0
  103. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-title.svelte +20 -0
  104. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal-trigger.svelte +24 -0
  105. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal.svelte +24 -0
  106. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/modal/modal.svelte.ts +32 -0
  107. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/separator/index.ts +7 -0
  108. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/separator/separator.svelte +21 -0
  109. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/index.ts +16 -0
  110. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs-content.svelte +17 -0
  111. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs-list.svelte +20 -0
  112. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs-trigger.svelte +20 -0
  113. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/components/ui/tabs/tabs.svelte +19 -0
  114. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/hooks/use-file-bindings.ts +189 -0
  115. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/react/JsonTreeCanvas.tsx +207 -0
  116. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/utils/config-format.ts +113 -0
  117. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/utils/utils.ts +21 -0
  118. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/lib/utils.ts +17 -0
  119. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/src/main.js +7 -0
  120. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/static/fonts/Inter-roman.var.woff2 +0 -0
  121. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/static/index.js +9719 -0
  122. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/static/style.css +1 -0
  123. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/static/vite.svg +1 -0
  124. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/svelte.config.js +8 -0
  125. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/tailwind.config.js +12 -0
  126. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/tsconfig.json +28 -0
  127. dr_widget-0.1.5/src/dr_widget/widgets/config_file_manager/vite.config.js +36 -0
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.3
2
+ Name: dr-widget
3
+ Version: 0.1.5
4
+ Summary: Widgets to use with marimo notebooks
5
+ Author: Danielle Rothermel
6
+ Author-email: Danielle Rothermel <danielle.rothermel@gmail.com>
7
+ Requires-Dist: anywidget>=0.9.18
8
+ Requires-Dist: marimo>=0.19.4
9
+ Requires-Dist: pydantic>=2.12.4
10
+ Requires-Dist: traitlets>=5.14.3
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+
14
+ # dr_widget
15
+
16
+ dr_widget is a hybrid Python/Svelte project for building reusable AnyWidget components that can be dropped into Marimo notebooks today and exported to full Svelte apps later. The repository currently ships a Config File Manager widget, but the layout is designed to host additional widgets.
17
+
18
+ ## Quick Start
19
+
20
+ ```bash
21
+ # Install JS dependencies (root + workspace)
22
+ bun install
23
+
24
+ # Live-reload the Config File Manager widget in a browser
25
+ bun run dev:config-file-manager
26
+
27
+ # Produce the optimized bundle used by AnyWidget
28
+ bun run build
29
+
30
+ # Build the Python distributions (wheel + sdist)
31
+ uv build
32
+
33
+ # Launch the Marimo demo notebook
34
+ marimo run notebooks/config_file_manager_widget.py
35
+ ```
36
+
37
+ Prerequisites: Bun ≥ 1.0, Node-compatible environment, Python ≥ 3.11 with `uv`, and Marimo ≥ 0.17.6.
38
+
39
+ ## Repository Layout
40
+
41
+ - `src/dr_widget/` – Python package exposing AnyWidget classes.
42
+ - `widgets/config_file_manager/` – widget workspace (Svelte source in `src/`, build output in `static/`).
43
+ - `src/ConfigFileManager.svelte` – orchestration layer wiring bindings into the panel components.
44
+ - `src/lib/hooks/use-file-bindings.ts` – shared logic for syncing AnyWidget traitlets.
45
+ - `src/lib/components/` – shadcn-style UI primitives and panels, including a config viewer card with both a tree view and graph view for JSON payloads.
46
+ - `docs/` – additional reference material (architecture, development workflows).
47
+ - `notebooks/config_file_manager_widget.py` – Marimo notebook that exercises the Config File Manager widget.
48
+
49
+ ## Documentation
50
+
51
+ - [Architecture Overview](docs/architecture.md) – how Python, AnyWidget, and Svelte fit together.
52
+ - [Development Workflow](docs/development.md) – commands for widget builds, packaging, and notebooks.
53
+ - [Repository Guidelines](AGENTS.md) – coding standards, contracts, and contribution checklist.
54
+
55
+ ## Contributing
56
+
57
+ 1. Work inside a dedicated branch.
58
+ 2. Run `bun run build`, `npx svelte-check`, and `uv build` before opening a PR.
59
+ 3. Update the notebook and docs when you add or change widget behaviour.
60
+ 4. Follow the commit and PR practices outlined in `AGENTS.md`.
61
+
62
+ Please open an issue if you hit build problems or want to discuss new widgets.
@@ -0,0 +1,49 @@
1
+ # dr_widget
2
+
3
+ dr_widget is a hybrid Python/Svelte project for building reusable AnyWidget components that can be dropped into Marimo notebooks today and exported to full Svelte apps later. The repository currently ships a Config File Manager widget, but the layout is designed to host additional widgets.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Install JS dependencies (root + workspace)
9
+ bun install
10
+
11
+ # Live-reload the Config File Manager widget in a browser
12
+ bun run dev:config-file-manager
13
+
14
+ # Produce the optimized bundle used by AnyWidget
15
+ bun run build
16
+
17
+ # Build the Python distributions (wheel + sdist)
18
+ uv build
19
+
20
+ # Launch the Marimo demo notebook
21
+ marimo run notebooks/config_file_manager_widget.py
22
+ ```
23
+
24
+ Prerequisites: Bun ≥ 1.0, Node-compatible environment, Python ≥ 3.11 with `uv`, and Marimo ≥ 0.17.6.
25
+
26
+ ## Repository Layout
27
+
28
+ - `src/dr_widget/` – Python package exposing AnyWidget classes.
29
+ - `widgets/config_file_manager/` – widget workspace (Svelte source in `src/`, build output in `static/`).
30
+ - `src/ConfigFileManager.svelte` – orchestration layer wiring bindings into the panel components.
31
+ - `src/lib/hooks/use-file-bindings.ts` – shared logic for syncing AnyWidget traitlets.
32
+ - `src/lib/components/` – shadcn-style UI primitives and panels, including a config viewer card with both a tree view and graph view for JSON payloads.
33
+ - `docs/` – additional reference material (architecture, development workflows).
34
+ - `notebooks/config_file_manager_widget.py` – Marimo notebook that exercises the Config File Manager widget.
35
+
36
+ ## Documentation
37
+
38
+ - [Architecture Overview](docs/architecture.md) – how Python, AnyWidget, and Svelte fit together.
39
+ - [Development Workflow](docs/development.md) – commands for widget builds, packaging, and notebooks.
40
+ - [Repository Guidelines](AGENTS.md) – coding standards, contracts, and contribution checklist.
41
+
42
+ ## Contributing
43
+
44
+ 1. Work inside a dedicated branch.
45
+ 2. Run `bun run build`, `npx svelte-check`, and `uv build` before opening a PR.
46
+ 3. Update the notebook and docs when you add or change widget behaviour.
47
+ 4. Follow the commit and PR practices outlined in `AGENTS.md`.
48
+
49
+ Please open an issue if you hit build problems or want to discuss new widgets.
@@ -0,0 +1,41 @@
1
+ [project]
2
+ name = "dr-widget"
3
+ version = "0.1.5"
4
+ description = "Widgets to use with marimo notebooks"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Danielle Rothermel", email = "danielle.rothermel@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.11"
10
+ dependencies = [
11
+ "anywidget>=0.9.18",
12
+ "marimo>=0.19.4",
13
+ "pydantic>=2.12.4",
14
+ "traitlets>=5.14.3",
15
+ ]
16
+
17
+ [build-system]
18
+ requires = ["uv_build>=0.9.7,<0.10.0"]
19
+ build-backend = "uv_build"
20
+
21
+ [tool.uv.build-backend]
22
+ source-include = ["src/dr_widget/widgets/config_file_manager/static/**"]
23
+ source-exclude = [
24
+ "src/dr_widget/widgets/config_file_manager/node_modules",
25
+ "src/dr_widget/widgets/config_file_manager/node_modules/**",
26
+ ]
27
+
28
+ [dependency-groups]
29
+ dev = [
30
+ "python-lsp-server>=1.13.1",
31
+ "ruff>=0.14.4",
32
+ "watchdog>=6.0.0",
33
+ ]
34
+
35
+ [tool.ruff]
36
+ include = ["src/**/*.py", "notebooks/**/*.py"]
37
+ exclude = ["tests/**/*.py", "dist/", "docs/"]
38
+ line-length = 88
39
+
40
+ [tool.basedpyright]
41
+ typeCheckingMode = "off"
@@ -0,0 +1,5 @@
1
+ """Top-level package for dr_widget."""
2
+
3
+ from .widgets.config_file_manager import ConfigFileManager
4
+
5
+ __all__ = ["ConfigFileManager"]
File without changes
@@ -0,0 +1,5 @@
1
+ """Widget exports for dr_widget."""
2
+
3
+ from .config_file_manager import ConfigFileManager
4
+
5
+ __all__ = ["ConfigFileManager"]
@@ -0,0 +1,24 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
@@ -0,0 +1,3 @@
1
+ {
2
+ "recommendations": ["svelte.svelte-vscode"]
3
+ }
@@ -0,0 +1,89 @@
1
+ # Config File Manager Widget
2
+
3
+ The `dr_widget` package ships an AnyWidget-powered config file manager so notebooks can load, inspect, edit, and save JSON configuration blobs without leaving the browser. The frontend lives under `src/dr_widget/widgets/config_file_manager`, is built with Svelte + Vite via Bun, and syncs its state to Python traitlets.
4
+
5
+ ## Repository Layout
6
+
7
+ - `src/dr_widget/widgets/config_file_manager/__init__.py` – AnyWidget class with initialization helpers and traitlet contracts.
8
+ - `src/dr_widget/widgets/config_file_manager/src/` – Svelte workspace (components live under `lib/`).
9
+ - `src/dr_widget/widgets/config_file_manager/static/` – Built bundle consumed by AnyWidget.
10
+ - `notebooks/config_file_manager_widget.py` – Marimo demo that exercises the widget end-to-end.
11
+
12
+ ## Traitlets
13
+
14
+ | Traitlet | Direction | Description |
15
+ | --- | --- | --- |
16
+ | `current_state` | ↔ | JSON string representing **user data only** (no metadata). |
17
+ | `baseline_state` | ↔ | Last saved value of `current_state`, used for dirty detection/diffs. |
18
+ | `version` | ↔ | String metadata displayed in the UI and written to disk alongside `current_state` (now nested under `metadata.version`). |
19
+ | `config_file` | ↔ | Path to the backing file (may be relative today). |
20
+ | `config_file_display` | ↔ | UI-friendly label derived from `config_file`. |
21
+ | `files` | ↔ | JSON array of uploaded files (`{ name, size, type }`). |
22
+ | `file_count` | ← | Derived from `files.length`; read-only in the UI. |
23
+ | `error` | ↔ | User-facing error message cleared automatically on recovery. |
24
+
25
+ Python helper properties (`current_data`, `baseline_data`, `is_dirty`) expose parsed state for notebooks.
26
+
27
+ ## Initialization Patterns
28
+
29
+ ```python
30
+ ConfigFileManager() # empty widget, user loads file via UI
31
+
32
+ ConfigFileManager(config_dict={"orchard": ["Basin"]}, version="exp_v1")
33
+ # - current_state populated with data dict
34
+ # - baseline_state empty → dirty until saved
35
+ # - config_file defaults to "exp_v1.json"
36
+
37
+ ConfigFileManager(config_file="/path/to/existing.json")
38
+ # - loads and migrates the file into new format
39
+ # - baseline_state matches current_state (clean)
40
+ # - version pulled from file metadata
41
+
42
+ ConfigFileManager(
43
+ config_file="/tmp/new.json",
44
+ config_dict={"selections": {"foo": True}},
45
+ version="v2",
46
+ )
47
+ # - writes wrapped payload {metadata:{version,saved_at},data}
48
+ # - baseline_state matches current_state
49
+ ```
50
+
51
+ Files saved through the UI (or via `config_file` + `config_dict`) are always written as:
52
+
53
+ ```json
54
+ {
55
+ "metadata": {
56
+ "version": "v1",
57
+ "saved_at": "2025-11-12T10:30:00Z"
58
+ },
59
+ "data": { ... user data ... }
60
+ }
61
+ ```
62
+
63
+ Older files that only contain `selections` or embed metadata at the top level are migrated into this structure when loaded, with legacy `version`/`saved_at` values relocated under `metadata`.
64
+
65
+ ## Frontend Behavior
66
+
67
+ - `use-file-bindings.ts` manages read/write loops for every synced traitlet so Marimo reactivity stays intact.
68
+ - `ConfigFileManager.svelte` derives dirty state by comparing `current_state` vs `baseline_state`, shows the currently loaded file name/version, and exposes Browse + Save panels.
69
+ - `SaveConfigPanel.svelte` wraps the current data with metadata before writing to disk (File System Access API when available, otherwise download).
70
+
71
+ ## Build & Test
72
+
73
+ ```bash
74
+ bun install
75
+ npx svelte-check --tsconfig src/dr_widget/widgets/config_file_manager/tsconfig.json
76
+ bun run build:config-file-manager
77
+ bun run build # aggregates widgets (currently same as line above)
78
+ uv build # packages the Python wheel with fresh static assets
79
+ ```
80
+
81
+ Manual validation: run `marimo run notebooks/config_file_manager_widget.py`, load/upload JSON configs (including legacy "selections" files), edit values, and save to disk. Confirm dirty badge toggles correctly and version changes propagate between the sidebar and save panel.
82
+
83
+ ## Contributing Tips
84
+
85
+ - Shared UI lives in `src/dr_widget/widgets/config_file_manager/src/lib/{components,hooks}`; prefer reusing hooks like `use-file-bindings`.
86
+ - Keep Tailwind utility classes grouped logically (layout → spacing → color → effects).
87
+ - Treat `node_modules/` as generated; never edit or commit them.
88
+ - Run `bunx prettier --write src/dr_widget/widgets/config_file_manager/src` before opening a PR.
89
+ - Document new traitlets or metadata fields in `docs/architecture.md` and notebook demos to keep Python + Svelte in sync.
@@ -0,0 +1,283 @@
1
+ """AnyWidget bindings for the config file manager widget."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import datetime, timezone
7
+ from pathlib import Path
8
+ from typing import Any, Dict, Optional
9
+
10
+ import anywidget
11
+ import traitlets
12
+
13
+ __all__ = ["ConfigFileManager"]
14
+
15
+ _STATIC_DIR = Path(__file__).parent / "static"
16
+
17
+
18
+ def _normalize_version(value: Optional[str]) -> str:
19
+ if value is None:
20
+ return "default_v0"
21
+ value = str(value).strip()
22
+ return value or "default_v0"
23
+
24
+
25
+ def _default_config_name(version: str) -> str:
26
+ safe = ''.join(ch if ch.isalnum() or ch in {"-", "_"} else "_" for ch in version)
27
+ safe = safe or "default_v0"
28
+ return f"{safe}.json"
29
+
30
+
31
+ def _resolve_config_path(value: str | Path | None) -> str:
32
+ if value is None:
33
+ return ""
34
+
35
+ raw = str(value).strip()
36
+ if not raw:
37
+ return ""
38
+
39
+ candidate = Path(raw).expanduser()
40
+ if not candidate.is_absolute():
41
+ candidate = Path.cwd() / candidate
42
+
43
+ return str(candidate.resolve())
44
+
45
+
46
+ def _serialize_user_state(data: Dict[str, Any]) -> str:
47
+ """Return a JSON string for the user-facing state or an empty string."""
48
+
49
+ if not data:
50
+ return ""
51
+
52
+ try:
53
+ return json.dumps(data, sort_keys=True, separators=(",", ":"))
54
+ except (TypeError, ValueError) as exc: # pragma: no cover - defensive
55
+ raise ValueError("Config state must be JSON serializable") from exc
56
+
57
+
58
+ def _ensure_mapping(value: Optional[Dict[str, Any]]) -> Dict[str, Any]:
59
+ if value is None:
60
+ return {}
61
+ if not isinstance(value, dict):
62
+ raise TypeError("config_dict must be a mapping of keys to values")
63
+ return value
64
+
65
+
66
+ def _utc_timestamp() -> str:
67
+ return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
68
+
69
+
70
+ def _normalize_payload(payload: Dict[str, Any]) -> Dict[str, Any]:
71
+ """Ensure the payload follows the new metadata/data contract."""
72
+
73
+ normalized: Dict[str, Any] = dict(payload)
74
+ data = normalized.get("data")
75
+
76
+ metadata_candidate = normalized.get("metadata")
77
+ if isinstance(metadata_candidate, dict):
78
+ metadata: Dict[str, Any] = dict(metadata_candidate)
79
+ else:
80
+ metadata = {}
81
+
82
+ for legacy_key in ("version", "saved_at"):
83
+ if legacy_key in normalized and legacy_key not in metadata:
84
+ metadata[legacy_key] = normalized.pop(legacy_key)
85
+
86
+ if not isinstance(data, dict):
87
+ user_data: Dict[str, Any] = {}
88
+
89
+ selections = normalized.pop("selections", None)
90
+ if isinstance(selections, dict):
91
+ user_data.setdefault("selections", selections)
92
+
93
+ for key in list(normalized.keys()):
94
+ if key in {"version", "saved_at", "data", "metadata"}:
95
+ continue
96
+ user_data[key] = normalized.pop(key)
97
+
98
+ data = user_data
99
+
100
+ normalized["metadata"] = metadata
101
+ normalized["data"] = data if isinstance(data, dict) else {}
102
+ return normalized
103
+
104
+
105
+ def _load_config_from_file(path: Path) -> Dict[str, Any]:
106
+ try:
107
+ raw = path.read_text(encoding="utf-8")
108
+ except FileNotFoundError:
109
+ raise
110
+ except OSError as exc: # pragma: no cover - filesystem specific
111
+ raise IOError(f"Unable to read config file: {path}") from exc
112
+
113
+ try:
114
+ parsed = json.loads(raw)
115
+ except json.JSONDecodeError as exc:
116
+ raise ValueError(f"Config file must contain valid JSON: {path}") from exc
117
+
118
+ if not isinstance(parsed, dict):
119
+ raise ValueError("Config file root must be a JSON object")
120
+
121
+ return _normalize_payload(parsed)
122
+
123
+
124
+ def _write_config_to_file(path: Path, *, data: Dict[str, Any], version: str) -> str:
125
+ saved_at = _utc_timestamp()
126
+ payload = {
127
+ "metadata": {
128
+ "version": version,
129
+ "saved_at": saved_at,
130
+ "save_path": str(path),
131
+ },
132
+ "data": data,
133
+ }
134
+
135
+ serialized = json.dumps(payload, indent=2, sort_keys=True)
136
+ path.parent.mkdir(parents=True, exist_ok=True)
137
+ path.write_text(serialized + "\n", encoding="utf-8")
138
+ return saved_at
139
+
140
+
141
+ def _file_binding_entry(path: Path) -> Dict[str, Any]:
142
+ try:
143
+ size = path.stat().st_size
144
+ except OSError:
145
+ size = 0
146
+
147
+ return {
148
+ "name": path.name,
149
+ "size": size,
150
+ "type": "application/json",
151
+ }
152
+
153
+
154
+ class ConfigFileManager(anywidget.AnyWidget):
155
+ """Config file manager widget for notebooks."""
156
+
157
+ # AnyWidget expects module references pointing at the built assets on disk.
158
+ _esm = _STATIC_DIR / "index.js"
159
+ _css = _STATIC_DIR / "style.css"
160
+
161
+ current_state = traitlets.Unicode("").tag(sync=True)
162
+ baseline_state = traitlets.Unicode("").tag(sync=True)
163
+ config_file = traitlets.Unicode("").tag(sync=True)
164
+ config_file_display = traitlets.Unicode("").tag(sync=True)
165
+ version = traitlets.Unicode("default_v0").tag(sync=True)
166
+ saved_at = traitlets.Unicode("").tag(sync=True)
167
+ files = traitlets.Unicode("[]").tag(sync=True)
168
+ file_count = traitlets.Int(0).tag(sync=True)
169
+ error = traitlets.Unicode("").tag(sync=True)
170
+
171
+ def __init__(
172
+ self,
173
+ config_file: str | Path | None = None,
174
+ config_dict: Optional[Dict[str, Any]] = None,
175
+ version: str = "default_v0",
176
+ **kwargs: Any,
177
+ ) -> None:
178
+ super().__init__(**kwargs)
179
+
180
+ normalized_version = _normalize_version(version)
181
+ self.version = normalized_version
182
+ self.current_state = ""
183
+ self.baseline_state = ""
184
+ self.config_file = ""
185
+ self.saved_at = ""
186
+
187
+ if config_file is None and config_dict is None:
188
+ return
189
+
190
+ if config_file is None:
191
+ user_data = _ensure_mapping(config_dict)
192
+ self.current_state = _serialize_user_state(user_data)
193
+ # No baseline until the data is persisted via the UI.
194
+ self.baseline_state = ""
195
+ default_name = _default_config_name(self.version)
196
+ default_path = _resolve_config_path(default_name)
197
+ self.config_file = default_path
198
+ return
199
+
200
+ resolved_path = _resolve_config_path(config_file)
201
+ path = Path(resolved_path)
202
+
203
+ if config_dict is not None:
204
+ if path.exists():
205
+ raise FileExistsError(
206
+ f"Config file already exists: {path}. Refusing to overwrite."
207
+ )
208
+
209
+ user_data = _ensure_mapping(config_dict)
210
+ _write_config_to_file(path, data=user_data, version=self.version)
211
+ elif not path.exists():
212
+ raise FileNotFoundError(f"Config file does not exist: {path}")
213
+
214
+ payload = _load_config_from_file(path)
215
+ file_data = payload.get("data")
216
+ user_state = file_data if isinstance(file_data, dict) else {}
217
+ serialized_state = _serialize_user_state(user_state)
218
+
219
+ metadata = payload.get("metadata")
220
+ if not isinstance(metadata, dict):
221
+ metadata = {}
222
+
223
+ payload_version = metadata.get("version")
224
+ if payload_version is not None:
225
+ version_str = _normalize_version(str(payload_version))
226
+ self.version = version_str
227
+
228
+ self.config_file = str(path)
229
+ self.current_state = serialized_state
230
+ self.baseline_state = serialized_state
231
+ saved_at_value = metadata.get("saved_at")
232
+ if saved_at_value:
233
+ self.saved_at = str(saved_at_value)
234
+ else:
235
+ self.saved_at = ""
236
+
237
+ file_entry = _file_binding_entry(path)
238
+ self.files = json.dumps([file_entry])
239
+ self.file_count = 1
240
+
241
+ def _parse_state(self, value: str) -> Dict[str, Any]:
242
+ if not value:
243
+ return {}
244
+
245
+ try:
246
+ parsed = json.loads(value)
247
+ except json.JSONDecodeError:
248
+ return {}
249
+
250
+ if isinstance(parsed, dict):
251
+ return parsed
252
+
253
+ return {}
254
+
255
+ @property
256
+ def current_data(self) -> Dict[str, Any]:
257
+ """Return the parsed current_state JSON payload as a dict."""
258
+
259
+ return self._parse_state(self.current_state)
260
+
261
+ @property
262
+ def baseline_data(self) -> Dict[str, Any]:
263
+ """Return the parsed baseline_state JSON payload as a dict."""
264
+
265
+ return self._parse_state(self.baseline_state)
266
+
267
+ @property
268
+ def is_dirty(self) -> bool:
269
+ """True if the current state differs from the last saved baseline."""
270
+
271
+ return self.current_data != self.baseline_data
272
+
273
+ @traitlets.validate("config_file")
274
+ def _validate_config_file(self, proposal: traitlets.Bunch) -> str:
275
+ return _resolve_config_path(proposal["value"])
276
+
277
+ @traitlets.observe("config_file")
278
+ def _observe_config_file(self, change: traitlets.Bunch) -> None:
279
+ value = change["new"]
280
+ if value:
281
+ self.config_file_display = Path(value).name
282
+ else:
283
+ self.config_file_display = ""
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://shadcn-svelte.com/schema.json",
3
+ "tailwind": {
4
+ "css": "src/app.css",
5
+ "baseColor": "slate"
6
+ },
7
+ "aliases": {
8
+ "components": "$lib/components",
9
+ "utils": "$lib/utils",
10
+ "ui": "$lib/components/ui",
11
+ "hooks": "$lib/hooks",
12
+ "lib": "$lib"
13
+ },
14
+ "typescript": true,
15
+ "registry": "https://shadcn-svelte.com/registry"
16
+ }
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>File Drop Widget - Dev Preview</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.js"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://unpkg.com/jsrepo@2.5.1/schemas/project-config.json",
3
+ "repos": ["@ieedan/shadcn-svelte-extras"],
4
+ "includeTests": false,
5
+ "includeDocs": false,
6
+ "watermark": true,
7
+ "formatter": "prettier",
8
+ "configFiles": {
9
+ "Shadcn Svelte Extras Cursor Rules": "./../../../../.cursor/rules/shadcn-svelte-extras.mdc"
10
+ },
11
+ "paths": {
12
+ "*": "./src/lib",
13
+ "ui": "./src/lib/components/ui",
14
+ "actions": "./src/lib/actions",
15
+ "hooks": "./src/lib/hooks",
16
+ "utils": "./src/lib/utils"
17
+ }
18
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@dr-widget/config-file-manager",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "vite build",
8
+ "dev": "vite dev",
9
+ "preview": "vite preview",
10
+ "check": "svelte-check --watch --tsconfig ./tsconfig.json"
11
+ },
12
+ "dependencies": {
13
+ "@anywidget/svelte": "^0.1.0",
14
+ "@zerodevx/svelte-json-view": "^1.0.11",
15
+ "elkjs": "^0.11.0",
16
+ "lucide-svelte": "^0.552.0",
17
+ "react": "18.2.0",
18
+ "react-dom": "18.2.0",
19
+ "react-zoom-pan-pinch": "^3.7.0",
20
+ "reaflow": "^5.4.1",
21
+ "svelte": "^5.43.2"
22
+ },
23
+ "devDependencies": {
24
+ "@internationalized/date": "^3.8.1",
25
+ "@lucide/svelte": "^0.544.0",
26
+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
27
+ "@tailwindcss/postcss": "^4.0.9",
28
+ "@types/bun": "latest",
29
+ "@types/react": "18.2.74",
30
+ "@types/react-dom": "18.2.25",
31
+ "@vitejs/plugin-react-swc": "3.7.0",
32
+ "autoprefixer": "^10.4.20",
33
+ "bits-ui": "^2.11.0",
34
+ "clsx": "^2.1.1",
35
+ "postcss": "^8.4.49",
36
+ "prettier": "^3.6.2",
37
+ "runed": "^0.31.1",
38
+ "svelte-check": "^4.3.3",
39
+ "tailwind-merge": "^3.3.1",
40
+ "tailwind-variants": "^3.1.1",
41
+ "tailwindcss": "^4.1.16",
42
+ "tw-animate-css": "^1.4.0",
43
+ "vaul-svelte": "1.0.0-next.7",
44
+ "vite": "^5.4.11"
45
+ },
46
+ "peerDependencies": {
47
+ "typescript": "^5.9.3"
48
+ }
49
+ }
@@ -0,0 +1,6 @@
1
+ import tailwindcss from '@tailwindcss/postcss';
2
+ import autoprefixer from 'autoprefixer';
3
+
4
+ export default {
5
+ plugins: [tailwindcss(), autoprefixer()]
6
+ };