jac-client 0.2.10__py3-none-any.whl → 0.2.12__py3-none-any.whl

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 (85) hide show
  1. jac_client/examples/all-in-one/button.jac +4 -3
  2. jac_client/examples/all-in-one/components/CategoryFilter.jac +36 -24
  3. jac_client/examples/all-in-one/components/Header.jac +12 -8
  4. jac_client/examples/all-in-one/components/ProfitOverview.jac +49 -35
  5. jac_client/examples/all-in-one/components/Summary.jac +59 -36
  6. jac_client/examples/all-in-one/components/TransactionForm.jac +142 -112
  7. jac_client/examples/all-in-one/components/TransactionItem.jac +37 -30
  8. jac_client/examples/all-in-one/components/TransactionList.jac +33 -26
  9. jac_client/examples/all-in-one/components/button.jac +4 -3
  10. jac_client/examples/all-in-one/components/navigation.jac +111 -117
  11. jac_client/examples/all-in-one/constants/categories.jac +23 -24
  12. jac_client/examples/all-in-one/constants/clients.jac +7 -8
  13. jac_client/examples/all-in-one/context/BudgetContext.jac +9 -6
  14. jac_client/examples/all-in-one/hooks/useBudget.jac +18 -12
  15. jac_client/examples/all-in-one/hooks/useLocalStorage.jac +14 -13
  16. jac_client/examples/all-in-one/main.jac +340 -371
  17. jac_client/examples/all-in-one/pages/BudgetPlanner.jac +19 -12
  18. jac_client/examples/all-in-one/pages/FeaturesTest.jac +31 -15
  19. jac_client/examples/all-in-one/pages/LandingPage.jac +113 -90
  20. jac_client/examples/all-in-one/pages/budget_planner_ui.cl.jac +34 -39
  21. jac_client/examples/all-in-one/pages/features_test_ui.cl.jac +464 -352
  22. jac_client/examples/all-in-one/pages/loginPage.jac +114 -119
  23. jac_client/examples/all-in-one/pages/nestedDemo.jac +43 -50
  24. jac_client/examples/all-in-one/pages/notFound.jac +14 -15
  25. jac_client/examples/all-in-one/pages/signupPage.jac +113 -119
  26. jac_client/examples/all-in-one/utils/formatters.jac +5 -8
  27. jac_client/examples/asset-serving/css-with-image/main.jac +77 -73
  28. jac_client/examples/asset-serving/image-asset/main.jac +47 -46
  29. jac_client/examples/asset-serving/import-alias/main.jac +93 -95
  30. jac_client/examples/basic/main.jac +17 -15
  31. jac_client/examples/basic-auth/main.jac +246 -254
  32. jac_client/examples/basic-auth-with-router/main.jac +272 -285
  33. jac_client/examples/basic-full-stack/main.jac +245 -242
  34. jac_client/examples/css-styling/js-styling/main.jac +41 -62
  35. jac_client/examples/css-styling/material-ui/main.jac +90 -90
  36. jac_client/examples/css-styling/pure-css/main.jac +35 -44
  37. jac_client/examples/css-styling/sass-example/main.jac +35 -44
  38. jac_client/examples/css-styling/styled-components/main.jac +38 -47
  39. jac_client/examples/css-styling/tailwind-example/main.jac +54 -43
  40. jac_client/examples/full-stack-with-auth/main.jac +407 -433
  41. jac_client/examples/little-x/main.jac +306 -344
  42. jac_client/examples/little-x/src/submit-button.jac +15 -14
  43. jac_client/examples/nested-folders/nested-advance/main.jac +18 -27
  44. jac_client/examples/nested-folders/nested-advance/src/ButtonRoot.jac +4 -6
  45. jac_client/examples/nested-folders/nested-advance/src/level1/ButtonSecondL.jac +9 -13
  46. jac_client/examples/nested-folders/nested-advance/src/level1/Card.jac +29 -32
  47. jac_client/examples/nested-folders/nested-advance/src/level1/level2/ButtonThirdL.jac +12 -18
  48. jac_client/examples/nested-folders/nested-basic/main.jac +7 -5
  49. jac_client/examples/nested-folders/nested-basic/src/button.jac +4 -3
  50. jac_client/examples/nested-folders/nested-basic/src/components/button.jac +4 -3
  51. jac_client/examples/ts-support/main.jac +26 -26
  52. jac_client/examples/with-router/main.jac +186 -223
  53. jac_client/plugin/client_runtime.cl.jac +5 -3
  54. jac_client/plugin/impl/client_runtime.impl.jac +1 -1
  55. jac_client/plugin/plugin_config.jac +53 -99
  56. jac_client/plugin/src/__init__.jac +0 -2
  57. jac_client/plugin/src/compiler.jac +0 -1
  58. jac_client/plugin/src/impl/compiler.impl.jac +49 -17
  59. jac_client/plugin/src/impl/jac_to_js.impl.jac +5 -1
  60. jac_client/plugin/src/impl/package_installer.impl.jac +20 -20
  61. jac_client/plugin/src/impl/vite_bundler.impl.jac +146 -84
  62. jac_client/plugin/src/targets/impl/desktop_target.impl.jac +54 -41
  63. jac_client/plugin/utils/__init__.jac +3 -0
  64. jac_client/plugin/utils/bun_installer.jac +16 -0
  65. jac_client/plugin/utils/client_deps.jac +14 -0
  66. jac_client/plugin/utils/impl/bun_installer.impl.jac +99 -0
  67. jac_client/plugin/utils/impl/client_deps.impl.jac +73 -0
  68. jac_client/templates/client.jacpack +0 -4
  69. jac_client/templates/fullstack.jacpack +1 -5
  70. jac_client/tests/conftest.py +56 -41
  71. jac_client/tests/fixtures/spawn_test/app.jac +49 -52
  72. jac_client/tests/fixtures/with-ts/app.jac +27 -27
  73. jac_client/tests/test_cli.py +71 -6
  74. jac_client/tests/test_helpers.py +11 -18
  75. jac_client/tests/test_it.py +1 -1
  76. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/METADATA +5 -5
  77. jac_client-0.2.12.dist-info/RECORD +115 -0
  78. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/WHEEL +1 -1
  79. jac_client/plugin/src/babel_processor.jac +0 -18
  80. jac_client/plugin/src/impl/babel_processor.impl.jac +0 -89
  81. jac_client/plugin/utils/impl/node_installer.impl.jac +0 -249
  82. jac_client/plugin/utils/node_installer.jac +0 -41
  83. jac_client-0.2.10.dist-info/RECORD +0 -115
  84. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/entry_points.txt +0 -0
  85. {jac_client-0.2.10.dist-info → jac_client-0.2.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,99 @@
1
+ """Implementation of Bun installer utility."""
2
+
3
+ """Check if bun is available, add to PATH if needed, or prompt to install."""
4
+ impl ensure_bun_available -> bool {
5
+ import os;
6
+ import shutil;
7
+ # Check if bun is already in PATH
8
+ if shutil.which("bun") {
9
+ return True;
10
+ }
11
+ # Check if bun exists at ~/.bun/bin but not in PATH
12
+ bun_bin = os.path.expanduser("~/.bun/bin");
13
+ bun_path = os.path.join(bun_bin, "bun");
14
+ if os.path.exists(bun_path) {
15
+ # Add to PATH for current process
16
+ current_path = os.environ.get("PATH", "");
17
+ os.environ["PATH"] = f"{bun_bin}:{current_path}";
18
+ return True;
19
+ }
20
+ # Bun not found - prompt to install
21
+ return prompt_install_bun();
22
+ }
23
+
24
+ """Prompt user to install Bun and install if confirmed."""
25
+ impl prompt_install_bun -> bool {
26
+ import os;
27
+ import subprocess;
28
+ import sys;
29
+ import shutil;
30
+ print("\n ⚠ Bun is required but not installed.", file=sys.stderr);
31
+ print(
32
+ " Bun is a fast JavaScript runtime used for package management and bundling.",
33
+ file=sys.stderr
34
+ );
35
+ print(" Learn more: https://bun.sh\n", file=sys.stderr);
36
+ try {
37
+ response = input(" Install Bun now? [Y/n]: ").strip().lower();
38
+ } except (EOFError, KeyboardInterrupt) {
39
+ print("", file=sys.stderr);
40
+ return False;
41
+ }
42
+ if response and response not in ('y', 'yes', '') {
43
+ return False;
44
+ }
45
+ print("\n ⏳ Installing Bun...", flush=True);
46
+ try {
47
+ # Check if curl is available
48
+ subprocess.run(
49
+ ["curl", "--version"], capture_output=True, check=True, timeout=5
50
+ );
51
+
52
+ # Install Bun via official script
53
+ result = subprocess.run(
54
+ ["sh", "-c", "curl -fsSL https://bun.sh/install | bash"],
55
+ timeout=120,
56
+ check=False
57
+ );
58
+
59
+ if result.returncode == 0 {
60
+ print(" ✔ Bun installed successfully!", flush=True);
61
+ # Add Bun to PATH for current session
62
+ bun_bin = os.path.expanduser("~/.bun/bin");
63
+ if os.path.exists(bun_bin) {
64
+ current_path = os.environ.get("PATH", "");
65
+ os.environ["PATH"] = f"{bun_bin}:{current_path}";
66
+ # Verify installation
67
+ if shutil.which("bun") {
68
+ verify = subprocess.run(
69
+ ["bun", "--version"], capture_output=True, text=True
70
+ );
71
+ if verify.returncode == 0 {
72
+ print(f" ✔ Verified: Bun {verify.stdout.strip()}", flush=True);
73
+ return True;
74
+ }
75
+ }
76
+ }
77
+ print(
78
+ " ⚠ Bun installed but may require terminal restart.", file=sys.stderr
79
+ );
80
+ print(" Run: source ~/.bashrc (or restart terminal)", file=sys.stderr);
81
+ return True;
82
+ } else {
83
+ print(" ✖ Bun installation failed.", file=sys.stderr);
84
+ return False;
85
+ }
86
+ } except subprocess.TimeoutExpired {
87
+ print(" ✖ Installation timed out.", file=sys.stderr);
88
+ return False;
89
+ } except FileNotFoundError {
90
+ print(
91
+ " ✖ curl not found. Please install Bun manually: https://bun.sh",
92
+ file=sys.stderr
93
+ );
94
+ return False;
95
+ } except Exception as e {
96
+ print(f" ✖ Installation failed: {e}", file=sys.stderr);
97
+ return False;
98
+ }
99
+ }
@@ -0,0 +1,73 @@
1
+ """Implementation of client dependency checker utility."""
2
+
3
+ """Check if client npm dependencies are configured, prompt to install if missing."""
4
+ impl ensure_client_deps(config_loader: object) -> bool {
5
+ package_config = config_loader.get_package_config();
6
+ dependencies = package_config.get('dependencies', {});
7
+ dev_dependencies = package_config.get('devDependencies', {});
8
+ # If either deps or devDeps has entries, assume configured
9
+ if dependencies or dev_dependencies {
10
+ return True;
11
+ }
12
+ # No deps configured — prompt the user
13
+ return prompt_install_client_deps(config_loader);
14
+ }
15
+
16
+ """Prompt user to install default jac-client npm dependencies."""
17
+ def prompt_install_client_deps(config_loader: object) -> bool {
18
+ import sys;
19
+ print("\n ⚠ Client dependencies are not configured.", file=sys.stderr);
20
+ print(
21
+ " jac-client requires npm packages (react, vite, etc.) to build client pages.",
22
+ file=sys.stderr
23
+ );
24
+ print(" These will be added to your jac.toml file.\n", file=sys.stderr);
25
+ try {
26
+ response = input(" Install default client dependencies now? [Y/n]: ").strip().lower();
27
+ } except (EOFError, KeyboardInterrupt) {
28
+ print("", file=sys.stderr);
29
+ return False;
30
+ }
31
+ if response and response not in ('y', 'yes', '') {
32
+ print(
33
+ "\n To configure manually, add [dependencies.npm] to your jac.toml.",
34
+ file=sys.stderr
35
+ );
36
+ print(
37
+ " Or create a new project with: jac create --use client\n",
38
+ file=sys.stderr
39
+ );
40
+ return False;
41
+ }
42
+ # Default runtime dependencies
43
+ default_deps: dict[str, str] = {
44
+ 'react': '^18.2.0',
45
+ 'react-dom': '^18.2.0',
46
+ 'react-router-dom': '^6.22.0',
47
+ 'react-error-boundary': '^5.0.0'
48
+ };
49
+ # Default build infrastructure dependencies
50
+ default_dev_deps: dict[str, str] = {
51
+ 'vite': '^6.4.1',
52
+ '@vitejs/plugin-react': '^4.2.1',
53
+ 'typescript': '^5.3.3',
54
+ '@types/react': '^18.2.0',
55
+ '@types/react-dom': '^18.2.0'
56
+ };
57
+ print("\n ⏳ Adding default client dependencies to jac.toml...", flush=True);
58
+ try {
59
+ for (name, version) in default_deps.items() {
60
+ config_loader.add_dependency(name, version, is_dev=False);
61
+ }
62
+ for (name, version) in default_dev_deps.items() {
63
+ config_loader.add_dependency(name, version, is_dev=True);
64
+ }
65
+ config_loader.save();
66
+ config_loader.invalidate();
67
+ print(" ✔ Default client dependencies added to jac.toml\n", flush=True);
68
+ return True;
69
+ } except Exception as e {
70
+ print(f" ✖ Failed to add dependencies: {e}", file=sys.stderr);
71
+ return False;
72
+ }
73
+ }
@@ -33,10 +33,6 @@
33
33
  "assets",
34
34
  ".jac/client"
35
35
  ],
36
- "gitignore_entries": [
37
- "# Ignore all build artifacts in .jac directory",
38
- "*"
39
- ],
40
36
  "root_gitignore_entries": [
41
37
  "# Jac project",
42
38
  "packages/",
@@ -26,7 +26,7 @@
26
26
  "files": {
27
27
  "main.jac": "\"\"\"{{name}} - Entry Point.\n\nCombines server-side endpoints with client-side UI.\n\"\"\"\n\n# Import server-side data models and walkers\nsv {\n import from endpoints { Todo, AddTodo, ListTodos, ToggleTodo, DeleteTodo }\n}\n\n# Client-side UI\ncl {\n import from .frontend { app as ClientApp }\n\n def:pub app -> any {\n return\n <ClientApp />;\n }\n}\n",
28
28
  "endpoints.sv.jac": "\"\"\"Todo App - Server-Side Data Layer.\n\nEach user gets their own todo list automatically!\nWhen authenticated, `root spawn` uses the user's personal root node.\n\"\"\"\n\n# ========================================\n# Data Models\n# ========================================\nnode Todo {\n has id: str,\n title: str,\n completed: bool = False;\n}\n\nglob todo_counter: int = 0;\n\n# ========================================\n# Walkers (API Operations)\n# ========================================\nwalker:priv AddTodo {\n \"\"\"Add a new todo item to the user's list.\"\"\"\n has title: str;\n\n can create with `root entry {\n global todo_counter;\n todo_counter += 1;\n new_id = \"todo_\" + str(todo_counter);\n new_todo = here ++> Todo(id=new_id, title=self.title, completed=False);\n if new_todo {\n report new_todo[0] ;\n }\n }\n}\n\nwalker:priv ListTodos {\n \"\"\"List all todo items for the current user.\"\"\"\n has todos: list = [];\n\n can collect with `root entry {\n visit [-->];\n }\n\n can gather with Todo entry {\n self.todos.append(\n {\"id\": here.id, \"title\": here.title, \"completed\": here.completed}\n );\n }\n\n can report_all with `root exit {\n report self.todos ;\n }\n}\n\nwalker:priv ToggleTodo {\n \"\"\"Toggle a todo's completed status.\"\"\"\n has todo_id: str;\n\n can search with `root entry {\n visit [-->];\n }\n\n can toggle with Todo entry {\n if here.id == self.todo_id {\n here.completed = not here.completed;\n report {\"id\": here.id, \"completed\": here.completed} ;\n }\n }\n}\n\nwalker:priv DeleteTodo {\n \"\"\"Delete a todo item.\"\"\"\n has todo_id: str;\n\n can search with `root entry {\n visit [-->];\n }\n\n can delete with Todo entry {\n if here.id == self.todo_id {\n del here ;\n report {\"deleted\": self.todo_id} ;\n }\n }\n}\n",
29
- "frontend.cl.jac": "\"\"\"Todo App - Client-Side UI.\"\"\"\n\nimport from react { useEffect }\nimport from \"@jac-client/utils\" { jacSignup, jacLogin, jacLogout, jacIsLoggedIn }\n\nimport from .components.TodoItem { TodoItem }\nimport from .components.AuthForm { AuthForm }\n\nsv import from endpoints { AddTodo, ListTodos, ToggleTodo, DeleteTodo }\n\ndef:pub app -> any {\n has isLoggedIn: bool = False,\n checkingAuth: bool = True,\n isSignup: bool = False,\n username: str = \"\",\n password: str = \"\",\n error: str = \"\",\n loading: bool = False,\n todos: list = [],\n newTodoText: str = \"\",\n todosLoading: bool = True;\n\n # Check login status on mount\n useEffect(lambda -> None { isLoggedIn = jacIsLoggedIn();checkingAuth = False;}, []);\n\n # Load todos when logged in\n useEffect(\n lambda -> None { if isLoggedIn {\n fetchTodos();\n }},\n [isLoggedIn]\n );\n\n # Fetch todos from server\n async def fetchTodos -> None {\n todosLoading = True;\n result = root spawn ListTodos();\n todos = result.reports[0] if result.reports else [];\n todosLoading = False;\n }\n\n # Add a new todo\n async def addTodo -> None {\n if not newTodoText.trim() {\n return;\n }\n response = root spawn AddTodo(title=newTodoText);\n newTodo = response.reports[0];\n todos = todos.concat(\n [\n {\n \"id\": newTodo.id,\n \"title\": newTodo.title,\n \"completed\": newTodo.completed\n }\n ]\n );\n newTodoText = \"\";\n }\n\n # Toggle todo completion\n async def toggleTodo(todoId: str) -> None {\n root spawn ToggleTodo(todo_id=todoId);\n todos = todos.map(\n lambda t: any -> any { if t.id == todoId {\n return {\"id\": t.id, \"title\": t.title, \"completed\": not t.completed};\n }return t; }\n );\n }\n\n # Delete a todo\n async def deleteTodo(todoId: str) -> None {\n root spawn DeleteTodo(todo_id=todoId);\n todos = todos.filter(lambda t: any -> bool { return t.id != todoId; });\n }\n\n # Handle login\n async def handleLogin -> None {\n error = \"\";\n if not username.trim() or not password {\n error = \"Please fill in all fields\";\n return;\n }\n loading = True;\n success = await jacLogin(username, password);\n loading = False;\n if success {\n isLoggedIn = True;\n username = \"\";\n password = \"\";\n } else {\n error = \"Invalid username or password\";\n }\n }\n\n # Handle signup\n async def handleSignup -> None {\n error = \"\";\n if not username.trim() or not password {\n error = \"Please fill in all fields\";\n return;\n }\n if password.length < 4 {\n error = \"Password must be at least 4 characters\";\n return;\n }\n loading = True;\n result = await jacSignup(username, password);\n loading = False;\n if result[\"success\"] {\n isLoggedIn = True;\n username = \"\";\n password = \"\";\n } else {\n error = result[\"error\"] if result[\"error\"] else \"Signup failed\";\n }\n }\n\n # Handle logout\n def handleLogout -> None {\n jacLogout();\n isLoggedIn = False;\n todos = [];\n username = \"\";\n password = \"\";\n error = \"\";\n }\n\n # Handle form submit\n async def handleSubmit(e: any) -> None {\n e.preventDefault();\n if isSignup {\n await handleSignup();\n } else {\n await handleLogin();\n }\n }\n\n # Handle enter key for todo input\n def handleTodoKeyPress(e: any) -> None {\n if e.key == \"Enter\" {\n addTodo();\n }\n }\n\n # Loading screen\n if checkingAuth {\n return\n <div\n style={{\n \"display\": \"flex\",\n \"justifyContent\": \"center\",\n \"alignItems\": \"center\",\n \"height\": \"100vh\",\n \"fontFamily\": \"system-ui, -apple-system, sans-serif\",\n \"color\": \"#666\"\n }}\n >\n Loading...\n </div>;\n }\n\n # Logged in - Show Todo List\n if isLoggedIn {\n return\n <div\n style={{\n \"maxWidth\": \"600px\",\n \"margin\": \"2rem auto\",\n \"padding\": \"2rem\",\n \"fontFamily\": \"system-ui, -apple-system, sans-serif\"\n }}\n >\n <div\n style={{\n \"display\": \"flex\",\n \"justifyContent\": \"space-between\",\n \"alignItems\": \"center\",\n \"marginBottom\": \"1.5rem\"\n }}\n >\n <h1 style={{\"margin\": \"0\", \"color\": \"#1a1a2e\"}}>\n My Todos\n </h1>\n <button\n onClick={handleLogout}\n style={{\n \"padding\": \"0.5rem 1rem\",\n \"fontSize\": \"0.9rem\",\n \"backgroundColor\": \"#f8f9fa\",\n \"color\": \"#666\",\n \"border\": \"1px solid #ddd\",\n \"borderRadius\": \"6px\",\n \"cursor\": \"pointer\"\n }}\n >\n Log Out\n </button>\n </div>\n <div\n style={{\n \"display\": \"flex\",\n \"gap\": \"0.5rem\",\n \"marginBottom\": \"1.5rem\"\n }}\n >\n <input\n type=\"text\"\n value={newTodoText}\n onChange={lambda e: any -> None { newTodoText = e.target.value;}}\n onKeyPress={handleTodoKeyPress}\n placeholder=\"What needs to be done?\"\n style={{\n \"flex\": \"1\",\n \"padding\": \"0.75rem 1rem\",\n \"fontSize\": \"1rem\",\n \"border\": \"2px solid #e0e0e0\",\n \"borderRadius\": \"8px\",\n \"outline\": \"none\"\n }}\n />\n <button\n onClick={lambda -> None { addTodo();}}\n style={{\n \"padding\": \"0.75rem 1.5rem\",\n \"fontSize\": \"1rem\",\n \"backgroundColor\": \"#4CAF50\",\n \"color\": \"white\",\n \"border\": \"none\",\n \"borderRadius\": \"8px\",\n \"cursor\": \"pointer\"\n }}\n >\n Add\n </button>\n </div>\n {(\n <div style={{\"color\": \"#666\", \"textAlign\": \"center\"}}>\n Loading todos...\n </div>\n )\n if todosLoading\n else None}\n {(\n <div>\n {(\n <p\n style={{\n \"color\": \"#888\",\n \"textAlign\": \"center\",\n \"padding\": \"2rem\"\n }}\n >\n No todos yet. Add one above!\n </p>\n )\n if todos.length == 0\n else None}{todos.map(\n lambda todo: any -> any { return\n <TodoItem\n key={todo.id}\n todo={todo}\n onToggle={toggleTodo}\n onDelete={deleteTodo}\n />; }\n )}\n </div>\n )\n if not todosLoading\n else None}\n <div\n style={{\n \"marginTop\": \"2rem\",\n \"paddingTop\": \"1rem\",\n \"borderTop\": \"1px solid #eee\",\n \"color\": \"#888\",\n \"fontSize\": \"0.9rem\"\n }}\n >\n {todos.filter(lambda t: any -> bool { return not t.completed; }).length} items left\n </div>\n </div>;\n }\n\n # Not logged in - Show Auth Form\n return\n <AuthForm\n isSignup={isSignup}\n username={username}\n password={password}\n error={error}\n loading={loading}\n onUsernameChange={lambda e: any -> None { username = e.target.value;}}\n onPasswordChange={lambda e: any -> None { password = e.target.value;}}\n onSubmit={handleSubmit}\n onToggleMode={lambda -> None { isSignup = not isSignup;error = \"\";}}\n />;\n}\n",
29
+ "frontend.cl.jac": "\"\"\"Todo App - Client-Side UI.\"\"\"\n\nimport from react { useEffect }\nimport from \"@jac/runtime\" { jacSignup, jacLogin, jacLogout, jacIsLoggedIn }\n\nimport from .components.TodoItem { TodoItem }\nimport from .components.AuthForm { AuthForm }\n\nsv import from endpoints { AddTodo, ListTodos, ToggleTodo, DeleteTodo }\n\ndef:pub app -> any {\n has isLoggedIn: bool = False,\n checkingAuth: bool = True,\n isSignup: bool = False,\n username: str = \"\",\n password: str = \"\",\n error: str = \"\",\n loading: bool = False,\n todos: list = [],\n newTodoText: str = \"\",\n todosLoading: bool = True;\n\n # Check login status on mount\n useEffect(lambda -> None { isLoggedIn = jacIsLoggedIn();checkingAuth = False;}, []);\n\n # Load todos when logged in\n useEffect(\n lambda -> None { if isLoggedIn {\n fetchTodos();\n }},\n [isLoggedIn]\n );\n\n # Fetch todos from server\n async def fetchTodos -> None {\n todosLoading = True;\n result = root spawn ListTodos();\n todos = result.reports[0] if result.reports else [];\n todosLoading = False;\n }\n\n # Add a new todo\n async def addTodo -> None {\n if not newTodoText.trim() {\n return;\n }\n response = root spawn AddTodo(title=newTodoText);\n newTodo = response.reports[0];\n todos = todos.concat(\n [\n {\n \"id\": newTodo.id,\n \"title\": newTodo.title,\n \"completed\": newTodo.completed\n }\n ]\n );\n newTodoText = \"\";\n }\n\n # Toggle todo completion\n async def toggleTodo(todoId: str) -> None {\n root spawn ToggleTodo(todo_id=todoId);\n todos = todos.map(\n lambda t: any -> any { if t.id == todoId {\n return {\"id\": t.id, \"title\": t.title, \"completed\": not t.completed};\n }return t; }\n );\n }\n\n # Delete a todo\n async def deleteTodo(todoId: str) -> None {\n root spawn DeleteTodo(todo_id=todoId);\n todos = todos.filter(lambda t: any -> bool { return t.id != todoId; });\n }\n\n # Handle login\n async def handleLogin -> None {\n error = \"\";\n if not username.trim() or not password {\n error = \"Please fill in all fields\";\n return;\n }\n loading = True;\n success = await jacLogin(username, password);\n loading = False;\n if success {\n isLoggedIn = True;\n username = \"\";\n password = \"\";\n } else {\n error = \"Invalid username or password\";\n }\n }\n\n # Handle signup\n async def handleSignup -> None {\n error = \"\";\n if not username.trim() or not password {\n error = \"Please fill in all fields\";\n return;\n }\n if password.length < 4 {\n error = \"Password must be at least 4 characters\";\n return;\n }\n loading = True;\n result = await jacSignup(username, password);\n loading = False;\n if result[\"success\"] {\n isLoggedIn = True;\n username = \"\";\n password = \"\";\n } else {\n error = result[\"error\"] if result[\"error\"] else \"Signup failed\";\n }\n }\n\n # Handle logout\n def handleLogout -> None {\n jacLogout();\n isLoggedIn = False;\n todos = [];\n username = \"\";\n password = \"\";\n error = \"\";\n }\n\n # Handle form submit\n async def handleSubmit(e: any) -> None {\n e.preventDefault();\n if isSignup {\n await handleSignup();\n } else {\n await handleLogin();\n }\n }\n\n # Handle enter key for todo input\n def handleTodoKeyPress(e: any) -> None {\n if e.key == \"Enter\" {\n addTodo();\n }\n }\n\n # Loading screen\n if checkingAuth {\n return\n <div\n style={{\n \"display\": \"flex\",\n \"justifyContent\": \"center\",\n \"alignItems\": \"center\",\n \"height\": \"100vh\",\n \"fontFamily\": \"system-ui, -apple-system, sans-serif\",\n \"color\": \"#666\"\n }}\n >\n Loading...\n </div>;\n }\n\n # Logged in - Show Todo List\n if isLoggedIn {\n return\n <div\n style={{\n \"maxWidth\": \"600px\",\n \"margin\": \"2rem auto\",\n \"padding\": \"2rem\",\n \"fontFamily\": \"system-ui, -apple-system, sans-serif\"\n }}\n >\n <div\n style={{\n \"display\": \"flex\",\n \"justifyContent\": \"space-between\",\n \"alignItems\": \"center\",\n \"marginBottom\": \"1.5rem\"\n }}\n >\n <h1 style={{\"margin\": \"0\", \"color\": \"#1a1a2e\"}}>\n My Todos\n </h1>\n <button\n onClick={handleLogout}\n style={{\n \"padding\": \"0.5rem 1rem\",\n \"fontSize\": \"0.9rem\",\n \"backgroundColor\": \"#f8f9fa\",\n \"color\": \"#666\",\n \"border\": \"1px solid #ddd\",\n \"borderRadius\": \"6px\",\n \"cursor\": \"pointer\"\n }}\n >\n Log Out\n </button>\n </div>\n <div\n style={{\n \"display\": \"flex\",\n \"gap\": \"0.5rem\",\n \"marginBottom\": \"1.5rem\"\n }}\n >\n <input\n type=\"text\"\n value={newTodoText}\n onChange={lambda e: any -> None { newTodoText = e.target.value;}}\n onKeyPress={handleTodoKeyPress}\n placeholder=\"What needs to be done?\"\n style={{\n \"flex\": \"1\",\n \"padding\": \"0.75rem 1rem\",\n \"fontSize\": \"1rem\",\n \"border\": \"2px solid #e0e0e0\",\n \"borderRadius\": \"8px\",\n \"outline\": \"none\"\n }}\n />\n <button\n onClick={lambda -> None { addTodo();}}\n style={{\n \"padding\": \"0.75rem 1.5rem\",\n \"fontSize\": \"1rem\",\n \"backgroundColor\": \"#4CAF50\",\n \"color\": \"white\",\n \"border\": \"none\",\n \"borderRadius\": \"8px\",\n \"cursor\": \"pointer\"\n }}\n >\n Add\n </button>\n </div>\n {(\n <div style={{\"color\": \"#666\", \"textAlign\": \"center\"}}>\n Loading todos...\n </div>\n )\n if todosLoading\n else None}\n {(\n <div>\n {(\n <p\n style={{\n \"color\": \"#888\",\n \"textAlign\": \"center\",\n \"padding\": \"2rem\"\n }}\n >\n No todos yet. Add one above!\n </p>\n )\n if todos.length == 0\n else None}{todos.map(\n lambda todo: any -> any { return\n <TodoItem\n key={todo.id}\n todo={todo}\n onToggle={toggleTodo}\n onDelete={deleteTodo}\n />; }\n )}\n </div>\n )\n if not todosLoading\n else None}\n <div\n style={{\n \"marginTop\": \"2rem\",\n \"paddingTop\": \"1rem\",\n \"borderTop\": \"1px solid #eee\",\n \"color\": \"#888\",\n \"fontSize\": \"0.9rem\"\n }}\n >\n {todos.filter(lambda t: any -> bool { return not t.completed; }).length} items left\n </div>\n </div>;\n }\n\n # Not logged in - Show Auth Form\n return\n <AuthForm\n isSignup={isSignup}\n username={username}\n password={password}\n error={error}\n loading={loading}\n onUsernameChange={lambda e: any -> None { username = e.target.value;}}\n onPasswordChange={lambda e: any -> None { password = e.target.value;}}\n onSubmit={handleSubmit}\n onToggleMode={lambda -> None { isSignup = not isSignup;error = \"\";}}\n />;\n}\n",
30
30
  "components/AuthForm.cl.jac": "\"\"\"Authentication form component for login/signup.\"\"\"\n\ndef:pub AuthForm(\n isSignup: bool,\n username: str,\n password: str,\n error: str,\n loading: bool,\n onUsernameChange: any,\n onPasswordChange: any,\n onSubmit: any,\n onToggleMode: any\n) -> any {\n return\n <div\n style={{\n \"maxWidth\": \"400px\",\n \"margin\": \"4rem auto\",\n \"padding\": \"2rem\",\n \"fontFamily\": \"system-ui, -apple-system, sans-serif\",\n \"backgroundColor\": \"#fff\",\n \"borderRadius\": \"12px\",\n \"boxShadow\": \"0 4px 6px rgba(0,0,0,0.1)\"\n }}\n >\n <h1\n style={{\n \"marginBottom\": \"0.5rem\",\n \"color\": \"#1a1a2e\",\n \"textAlign\": \"center\"\n }}\n >\n Jac Todo App\n </h1>\n <p\n style={{\n \"marginBottom\": \"1.5rem\",\n \"color\": \"#666\",\n \"textAlign\": \"center\"\n }}\n >\n {(\"Create an account\" if isSignup else \"Sign in to your account\")}\n </p>\n {(\n <div\n style={{\n \"padding\": \"0.75rem\",\n \"marginBottom\": \"1rem\",\n \"backgroundColor\": \"#fee\",\n \"color\": \"#c00\",\n \"borderRadius\": \"6px\",\n \"fontSize\": \"0.9rem\"\n }}\n >\n {error}\n </div>\n )\n if error\n else None}\n <form onSubmit={onSubmit}>\n <input\n type=\"text\"\n value={username}\n onChange={onUsernameChange}\n placeholder=\"Username\"\n style={{\n \"width\": \"100%\",\n \"padding\": \"0.75rem 1rem\",\n \"marginBottom\": \"0.75rem\",\n \"fontSize\": \"1rem\",\n \"border\": \"2px solid #e0e0e0\",\n \"borderRadius\": \"8px\",\n \"outline\": \"none\",\n \"boxSizing\": \"border-box\"\n }}\n />\n <input\n type=\"password\"\n value={password}\n onChange={onPasswordChange}\n placeholder=\"Password\"\n style={{\n \"width\": \"100%\",\n \"padding\": \"0.75rem 1rem\",\n \"marginBottom\": \"1rem\",\n \"fontSize\": \"1rem\",\n \"border\": \"2px solid #e0e0e0\",\n \"borderRadius\": \"8px\",\n \"outline\": \"none\",\n \"boxSizing\": \"border-box\"\n }}\n />\n <button\n type=\"submit\"\n disabled={loading}\n style={{\n \"width\": \"100%\",\n \"padding\": \"0.75rem\",\n \"fontSize\": \"1rem\",\n \"backgroundColor\": (\"#999\" if loading else \"#4CAF50\"),\n \"color\": \"white\",\n \"border\": \"none\",\n \"borderRadius\": \"8px\",\n \"cursor\": (\"not-allowed\" if loading else \"pointer\"),\n \"marginBottom\": \"1rem\"\n }}\n >\n {(\n (\n \"Processing...\"\n if loading\n else (\"Sign Up\" if isSignup else \"Log In\")\n )\n )}\n </button>\n </form>\n <p style={{\"textAlign\": \"center\", \"color\": \"#666\", \"fontSize\": \"0.9rem\"}}>\n {(\n \"Already have an account? \"\n if isSignup\n else \"Don't have an account? \"\n )}\n <span\n onClick={onToggleMode}\n style={{\n \"color\": \"#4CAF50\",\n \"cursor\": \"pointer\",\n \"fontWeight\": \"bold\"\n }}\n >\n {(\"Log In\" if isSignup else \"Sign Up\")}\n </span>\n </p>\n </div>;\n}\n",
31
31
  "components/TodoItem.cl.jac": "\"\"\"Todo item component for displaying a single todo.\"\"\"\n\ndef:pub TodoItem(todo: dict, onToggle: any, onDelete: any) -> any {\n return\n <div\n key={todo.id}\n style={{\n \"display\": \"flex\",\n \"alignItems\": \"center\",\n \"gap\": \"0.75rem\",\n \"padding\": \"1rem\",\n \"marginBottom\": \"0.5rem\",\n \"backgroundColor\": \"#f8f9fa\",\n \"borderRadius\": \"8px\",\n \"border\": \"1px solid #e9ecef\"\n }}\n >\n <input\n type=\"checkbox\"\n checked={todo.completed}\n onChange={lambda -> None { onToggle(todo.id);}}\n style={{\"width\": \"20px\", \"height\": \"20px\", \"cursor\": \"pointer\"}}\n />\n <span\n style={{\n \"flex\": \"1\",\n \"textDecoration\": (\"line-through\" if todo.completed else \"none\"),\n \"color\": (\"#888\" if todo.completed else \"#333\")\n }}\n >\n {todo.title}\n </span>\n <button\n onClick={lambda -> None { onDelete(todo.id);}}\n style={{\n \"padding\": \"0.5rem\",\n \"backgroundColor\": \"transparent\",\n \"color\": \"#dc3545\",\n \"border\": \"none\",\n \"cursor\": \"pointer\",\n \"fontSize\": \"1.2rem\"\n }}\n >\n x\n </button>\n </div>;\n}\n",
32
32
  "components/Button.cl.jac": "\"\"\"Button component for the Jac client application.\"\"\"\n\ndef:pub Button(\n label: str, onClick: any, variant: str = \"primary\", disabled: bool = False\n) -> any {\n base_styles = {\n \"padding\": \"0.75rem 1.5rem\",\n \"fontSize\": \"1rem\",\n \"fontWeight\": \"600\",\n \"borderRadius\": \"0.5rem\",\n \"border\": \"none\",\n \"cursor\": \"not-allowed\" if disabled else \"pointer\",\n \"transition\": \"all 0.2s ease\"\n };\n\n variant_styles = {\n \"primary\": {\n \"backgroundColor\": \"#9ca3af\" if disabled else \"#3b82f6\",\n \"color\": \"#ffffff\"\n },\n \"secondary\": {\n \"backgroundColor\": \"#e5e7eb\" if disabled else \"#6b7280\",\n \"color\": \"#ffffff\"\n }\n };\n\n return\n <button\n style={{** base_styles, ** variant_styles[variant]}}\n onClick={onClick}\n disabled={disabled}\n >\n {label}\n </button>;\n}\n",
@@ -36,10 +36,6 @@
36
36
  ".jac",
37
37
  "assets"
38
38
  ],
39
- "gitignore_entries": [
40
- "# Ignore all build artifacts in .jac directory",
41
- "*"
42
- ],
43
39
  "root_gitignore_entries": [
44
40
  "# Jac build artifacts",
45
41
  ".jac/",
@@ -1,9 +1,9 @@
1
1
  """Pytest configuration and shared fixtures for jac-client tests.
2
2
 
3
3
  This module provides session-scoped fixtures to optimize test execution by:
4
- 1. Running npm install once per session and caching node_modules
4
+ 1. Running bun install once per session and caching node_modules
5
5
  2. Providing shared Vite build infrastructure
6
- 3. Mocking npm install for tests that only need jac.toml manipulation
6
+ 3. Mocking bun install for tests that only need jac.toml manipulation
7
7
  """
8
8
 
9
9
  from __future__ import annotations
@@ -83,16 +83,15 @@ def _get_jac_command() -> list[str]:
83
83
  return [sys.executable, "-m", "jaclang"]
84
84
 
85
85
 
86
- def _get_env_with_npm() -> dict[str, str]:
87
- """Get environment dict with npm in PATH."""
86
+ def _get_env_with_bun() -> dict[str, str]:
87
+ """Get environment dict with bun in PATH."""
88
88
  env = os.environ.copy()
89
- # npm might be installed via nvm, ensure PATH includes common locations
90
- npm_path = shutil.which("npm")
91
- if npm_path:
92
- npm_dir = str(Path(npm_path).parent)
89
+ bun_path = shutil.which("bun")
90
+ if bun_path:
91
+ bun_dir = str(Path(bun_path).parent)
93
92
  current_path = env.get("PATH", "")
94
- if npm_dir not in current_path:
95
- env["PATH"] = f"{npm_dir}:{current_path}"
93
+ if bun_dir not in current_path:
94
+ env["PATH"] = f"{bun_dir}:{current_path}"
96
95
  return env
97
96
 
98
97
 
@@ -127,16 +126,16 @@ def reset_jac_machine(tmp_path: Path) -> Generator[None, None, None]:
127
126
  Jac.loaded_modules.clear()
128
127
 
129
128
 
130
- # Session-scoped cache for npm installation
131
- _npm_cache_dir: Path | None = None
129
+ # Session-scoped cache for bun installation
130
+ _bun_cache_dir: Path | None = None
132
131
 
133
132
 
134
133
  def _get_minimal_jac_toml() -> str:
135
- """Get minimal jac.toml content for npm cache setup."""
134
+ """Get minimal jac.toml content for bun cache setup."""
136
135
  return """[project]
137
- name = "npm-cache"
136
+ name = "bun-cache"
138
137
  version = "0.0.1"
139
- description = "Cached npm modules"
138
+ description = "Cached bun modules"
140
139
  entry-point = "app.jac"
141
140
 
142
141
  [plugins.client.vite.build]
@@ -145,28 +144,28 @@ minify = false
145
144
 
146
145
 
147
146
  @pytest.fixture(scope="session")
148
- def npm_cache_dir() -> Generator[Path, None, None]:
149
- """Session-scoped fixture that provides a directory with npm packages installed.
147
+ def bun_cache_dir() -> Generator[Path, None, None]:
148
+ """Session-scoped fixture that provides a directory with bun packages installed.
150
149
 
151
- This runs npm install once per test session and provides the path to the
150
+ This runs bun install once per test session and provides the path to the
152
151
  .jac/client/configs directory containing node_modules.
153
152
  """
154
- global _npm_cache_dir
153
+ global _bun_cache_dir
155
154
 
156
- if _npm_cache_dir is not None and _npm_cache_dir.exists():
157
- yield _npm_cache_dir
155
+ if _bun_cache_dir is not None and _bun_cache_dir.exists():
156
+ yield _bun_cache_dir
158
157
  return
159
158
 
160
159
  # Create a persistent temp directory for the session
161
- cache_dir = Path(tempfile.mkdtemp(prefix="jac_npm_cache_"))
160
+ cache_dir = Path(tempfile.mkdtemp(prefix="jac_bun_cache_"))
162
161
 
163
162
  # Create jac.toml
164
163
  jac_toml = cache_dir / "jac.toml"
165
164
  jac_toml.write_text(_get_minimal_jac_toml())
166
165
 
167
- # Run jac add --npm to install packages
166
+ # Run jac add --npm to install packages (flag name unchanged for backward compatibility)
168
167
  jac_cmd = _get_jac_command()
169
- env = _get_env_with_npm()
168
+ env = _get_env_with_bun()
170
169
  result = subprocess.run(
171
170
  [*jac_cmd, "add", "--npm"],
172
171
  cwd=cache_dir,
@@ -178,36 +177,44 @@ def npm_cache_dir() -> Generator[Path, None, None]:
178
177
  if result.returncode != 0:
179
178
  # Clean up on failure
180
179
  shutil.rmtree(cache_dir, ignore_errors=True)
181
- pytest.skip(f"Failed to set up npm cache: {result.stderr}")
180
+ pytest.skip(f"Failed to set up bun cache: {result.stderr}")
182
181
 
183
- _npm_cache_dir = cache_dir
182
+ _bun_cache_dir = cache_dir
184
183
  yield cache_dir
185
184
 
186
185
  # Cleanup after all tests complete
187
186
  shutil.rmtree(cache_dir, ignore_errors=True)
188
187
 
189
188
 
189
+ # Backward compatibility alias
190
+ @pytest.fixture(scope="session")
191
+ def npm_cache_dir(bun_cache_dir: Path) -> Generator[Path, None, None]:
192
+ """Backward compatibility alias for bun_cache_dir."""
193
+ yield bun_cache_dir
194
+
195
+
190
196
  @pytest.fixture
191
- def vite_project_dir(npm_cache_dir: Path, tmp_path: Path) -> Path:
197
+ def vite_project_dir(bun_cache_dir: Path, tmp_path: Path) -> Path:
192
198
  """Fixture that provides a project directory with pre-installed node_modules.
193
199
 
194
- This copies node_modules from the session cache instead of running npm install.
200
+ This copies node_modules from the session cache instead of running bun install.
195
201
  """
196
202
  # Create jac.toml in the temp directory
197
203
  jac_toml = tmp_path / "jac.toml"
198
204
  jac_toml.write_text(_get_minimal_jac_toml())
199
205
 
200
206
  # Copy .jac/client/configs directory (contains package.json)
201
- source_configs = npm_cache_dir / ".jac" / "client" / "configs"
207
+ source_configs = bun_cache_dir / ".jac" / "client" / "configs"
202
208
  dest_configs = tmp_path / ".jac" / "client" / "configs"
203
209
  if source_configs.exists():
204
210
  dest_configs.parent.mkdir(parents=True, exist_ok=True)
205
211
  shutil.copytree(source_configs, dest_configs, symlinks=True)
206
212
 
207
- # Copy node_modules from project root (npm installs there, not in .jac/client/configs)
208
- source_node_modules = npm_cache_dir / "node_modules"
209
- dest_node_modules = tmp_path / "node_modules"
213
+ # Copy node_modules from .jac/client/ (bun installs there)
214
+ source_node_modules = bun_cache_dir / ".jac" / "client" / "node_modules"
215
+ dest_node_modules = tmp_path / ".jac" / "client" / "node_modules"
210
216
  if source_node_modules.exists():
217
+ dest_node_modules.parent.mkdir(parents=True, exist_ok=True)
211
218
  shutil.copytree(source_node_modules, dest_node_modules, symlinks=True)
212
219
 
213
220
  # Create required directories
@@ -219,7 +226,7 @@ def vite_project_dir(npm_cache_dir: Path, tmp_path: Path) -> Path:
219
226
 
220
227
 
221
228
  @pytest.fixture
222
- def vite_project_with_antd(npm_cache_dir: Path, tmp_path: Path) -> Path:
229
+ def vite_project_with_antd(bun_cache_dir: Path, tmp_path: Path) -> Path:
223
230
  """Fixture that provides a project directory with antd pre-installed."""
224
231
  # Create jac.toml with antd dependency
225
232
  jac_toml_content = """[project]
@@ -238,21 +245,22 @@ antd = "^6.0.0"
238
245
  jac_toml.write_text(jac_toml_content)
239
246
 
240
247
  # Copy base .jac/client/configs first for faster install
241
- source_configs = npm_cache_dir / ".jac" / "client" / "configs"
248
+ source_configs = bun_cache_dir / ".jac" / "client" / "configs"
242
249
  dest_configs = tmp_path / ".jac" / "client" / "configs"
243
250
  if source_configs.exists():
244
251
  dest_configs.parent.mkdir(parents=True, exist_ok=True)
245
252
  shutil.copytree(source_configs, dest_configs, symlinks=True)
246
253
 
247
- # Copy base node_modules for faster install (npm will add antd on top)
248
- source_node_modules = npm_cache_dir / "node_modules"
249
- dest_node_modules = tmp_path / "node_modules"
254
+ # Copy base node_modules for faster install (bun will add antd on top)
255
+ source_node_modules = bun_cache_dir / ".jac" / "client" / "node_modules"
256
+ dest_node_modules = tmp_path / ".jac" / "client" / "node_modules"
250
257
  if source_node_modules.exists():
258
+ dest_node_modules.parent.mkdir(parents=True, exist_ok=True)
251
259
  shutil.copytree(source_node_modules, dest_node_modules, symlinks=True)
252
260
 
253
261
  # Install antd on top (uses cached node_modules as base)
254
262
  jac_cmd = _get_jac_command()
255
- env = _get_env_with_npm()
263
+ env = _get_env_with_bun()
256
264
  result = subprocess.run(
257
265
  [*jac_cmd, "add", "--npm"],
258
266
  cwd=tmp_path,
@@ -272,10 +280,10 @@ antd = "^6.0.0"
272
280
 
273
281
 
274
282
  @pytest.fixture
275
- def mock_npm_install():
276
- """Fixture that mocks npm install for tests that only test jac.toml manipulation.
283
+ def mock_bun_install():
284
+ """Fixture that mocks bun install for tests that only test jac.toml manipulation.
277
285
 
278
- Use this for CLI tests (add/remove commands) that don't need actual npm packages.
286
+ Use this for CLI tests (add/remove commands) that don't need actual packages.
279
287
  """
280
288
  with patch(
281
289
  "jac_client.plugin.src.package_installer.PackageInstaller._regenerate_and_install"
@@ -283,6 +291,13 @@ def mock_npm_install():
283
291
  yield mock
284
292
 
285
293
 
294
+ # Backward compatibility alias
295
+ @pytest.fixture
296
+ def mock_npm_install(mock_bun_install: Generator) -> Generator:
297
+ """Backward compatibility alias for mock_bun_install."""
298
+ yield mock_bun_install
299
+
300
+
286
301
  @pytest.fixture
287
302
  def cli_test_dir(tmp_path: Path) -> Path:
288
303
  """Fixture that provides a minimal test directory for CLI tests."""
@@ -9,7 +9,7 @@ walker test_walker {
9
9
  has message: str = "Hello from walker";
10
10
 
11
11
  can execute with `root entry {
12
- report {"result": self.message} ;
12
+ report {"result": self.message};
13
13
  }
14
14
  }
15
15
 
@@ -17,7 +17,7 @@ walker parameterized_walker {
17
17
  has value: int;
18
18
 
19
19
  can execute with `root entry {
20
- report {"computed": self.value * 2} ;
20
+ report {"computed": self.value * 2};
21
21
  }
22
22
  }
23
23
 
@@ -27,15 +27,13 @@ walker positional_walker {
27
27
  has metadata: dict = {};
28
28
 
29
29
  can execute with `root entry {
30
- report {"label": self.label, "count": self.count, "meta": self.metadata} ;
30
+ report {"label": self.label, "count": self.count, "meta": self.metadata};
31
31
  }
32
32
  }
33
33
 
34
34
  # Client-side code testing both spawn orderings
35
- cl import from react { useEffect }
36
-
37
35
  cl {
38
- def app() -> any {
36
+ def app() -> any {
39
37
  has standardResult: any = None;
40
38
  has standardComputed: any = None;
41
39
  has reverseResult: any = None;
@@ -44,7 +42,7 @@ cl {
44
42
  has positionalResult: any = None;
45
43
  has spreadResult: any = None;
46
44
 
47
- async def loadData() -> None {
45
+ async can with entry {
48
46
  # Test standard spawn order: node spawn walker()
49
47
  data1 = root spawn test_walker();
50
48
  standardResult = data1;
@@ -76,51 +74,50 @@ cl {
76
74
  spreadResult = data7;
77
75
  }
78
76
 
79
- useEffect(lambda -> None{ loadData();} , []);
80
-
81
- return <div>
82
- <h1>
83
- Spawn Operator Test
84
- </h1>
85
- <h2>
86
- Standard Order (node spawn walker)
87
- </h2>
88
- <div>
89
- Result: {JSON.stringify(standardResult)}
90
- </div>
91
- <div>
92
- Computed: {JSON.stringify(standardComputed)}
93
- </div>
94
- <h2>
95
- Reverse Order (walker spawn node)
96
- </h2>
97
- <div>
98
- Result: {JSON.stringify(reverseResult)}
99
- </div>
100
- <h2>
101
- UUID Spawn (uuid spawn walker)
102
- </h2>
103
- <div>
104
- Result: {JSON.stringify(uuidResult)}
105
- </div>
106
- <h2>
107
- Reverse UUID Spawn (walker spawn uuid)
108
- </h2>
109
- <div>
110
- Result: {JSON.stringify(reverseUuidResult)}
111
- </div>
112
- <h2>
113
- Positional Walker Arguments
114
- </h2>
115
- <div>
116
- Result: {JSON.stringify(positionalResult)}
117
- </div>
118
- <h2>
119
- Spread Walker Arguments
120
- </h2>
77
+ return
121
78
  <div>
122
- Result: {JSON.stringify(spreadResult)}
123
- </div>
124
- </div>;
79
+ <h1>
80
+ Spawn Operator Test
81
+ </h1>
82
+ <h2>
83
+ Standard Order (node spawn walker)
84
+ </h2>
85
+ <div>
86
+ Result: {JSON.stringify(standardResult)}
87
+ </div>
88
+ <div>
89
+ Computed: {JSON.stringify(standardComputed)}
90
+ </div>
91
+ <h2>
92
+ Reverse Order (walker spawn node)
93
+ </h2>
94
+ <div>
95
+ Result: {JSON.stringify(reverseResult)}
96
+ </div>
97
+ <h2>
98
+ UUID Spawn (uuid spawn walker)
99
+ </h2>
100
+ <div>
101
+ Result: {JSON.stringify(uuidResult)}
102
+ </div>
103
+ <h2>
104
+ Reverse UUID Spawn (walker spawn uuid)
105
+ </h2>
106
+ <div>
107
+ Result: {JSON.stringify(reverseUuidResult)}
108
+ </div>
109
+ <h2>
110
+ Positional Walker Arguments
111
+ </h2>
112
+ <div>
113
+ Result: {JSON.stringify(positionalResult)}
114
+ </div>
115
+ <h2>
116
+ Spread Walker Arguments
117
+ </h2>
118
+ <div>
119
+ Result: {JSON.stringify(spreadResult)}
120
+ </div>
121
+ </div>;
125
122
  }
126
123
  }