jac-client 0.2.5__py3-none-any.whl → 0.2.7__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 (61) hide show
  1. jac_client/examples/all-in-one/src/app.jac +473 -741
  2. jac_client/examples/all-in-one/src/components/CategoryFilter.jac +35 -0
  3. jac_client/examples/all-in-one/src/components/Header.jac +13 -0
  4. jac_client/examples/all-in-one/src/components/ProfitOverview.jac +50 -0
  5. jac_client/examples/all-in-one/src/components/Summary.jac +53 -0
  6. jac_client/examples/all-in-one/src/components/TransactionForm.jac +158 -0
  7. jac_client/examples/all-in-one/src/components/TransactionItem.jac +55 -0
  8. jac_client/examples/all-in-one/src/components/TransactionList.jac +37 -0
  9. jac_client/examples/all-in-one/src/components/navigation.jac +132 -0
  10. jac_client/examples/all-in-one/src/constants/categories.jac +37 -0
  11. jac_client/examples/all-in-one/src/constants/clients.jac +13 -0
  12. jac_client/examples/all-in-one/src/context/BudgetContext.jac +28 -0
  13. jac_client/examples/all-in-one/src/hooks/useBudget.jac +116 -0
  14. jac_client/examples/all-in-one/src/hooks/useLocalStorage.jac +36 -0
  15. jac_client/examples/all-in-one/src/pages/BudgetPlanner.cl.jac +70 -0
  16. jac_client/examples/all-in-one/src/pages/BudgetPlanner.jac +126 -0
  17. jac_client/examples/all-in-one/src/pages/FeaturesTest.cl.jac +552 -0
  18. jac_client/examples/all-in-one/src/pages/FeaturesTest.jac +126 -0
  19. jac_client/examples/all-in-one/src/pages/LandingPage.jac +101 -0
  20. jac_client/examples/all-in-one/src/pages/loginPage.jac +132 -0
  21. jac_client/examples/all-in-one/src/pages/nestedDemo.jac +61 -0
  22. jac_client/examples/all-in-one/src/pages/notFound.jac +24 -0
  23. jac_client/examples/all-in-one/src/pages/signupPage.jac +133 -0
  24. jac_client/examples/all-in-one/src/utils/formatters.jac +52 -0
  25. jac_client/examples/asset-serving/css-with-image/src/app.jac +3 -3
  26. jac_client/examples/asset-serving/image-asset/src/app.jac +3 -3
  27. jac_client/examples/asset-serving/import-alias/src/app.jac +3 -3
  28. jac_client/examples/basic/src/app.jac +3 -3
  29. jac_client/examples/basic-auth/src/app.jac +31 -37
  30. jac_client/examples/basic-auth-with-router/src/app.jac +16 -16
  31. jac_client/examples/basic-full-stack/src/app.jac +24 -30
  32. jac_client/examples/css-styling/js-styling/src/app.jac +5 -5
  33. jac_client/examples/css-styling/material-ui/src/app.jac +5 -5
  34. jac_client/examples/css-styling/pure-css/src/app.jac +5 -5
  35. jac_client/examples/css-styling/sass-example/src/app.jac +5 -5
  36. jac_client/examples/css-styling/styled-components/src/app.jac +5 -5
  37. jac_client/examples/css-styling/tailwind-example/src/app.jac +5 -5
  38. jac_client/examples/full-stack-with-auth/src/app.jac +16 -16
  39. jac_client/examples/ts-support/src/app.jac +4 -4
  40. jac_client/examples/with-router/src/app.jac +4 -4
  41. jac_client/plugin/cli.jac +155 -203
  42. jac_client/plugin/client_runtime.cl.jac +5 -1
  43. jac_client/plugin/impl/client.impl.jac +74 -12
  44. jac_client/plugin/plugin_config.jac +11 -11
  45. jac_client/plugin/src/compiler.jac +2 -1
  46. jac_client/plugin/src/impl/babel_processor.impl.jac +22 -17
  47. jac_client/plugin/src/impl/compiler.impl.jac +57 -18
  48. jac_client/plugin/src/impl/vite_bundler.impl.jac +66 -102
  49. jac_client/plugin/src/package_installer.jac +1 -1
  50. jac_client/plugin/src/vite_bundler.jac +1 -0
  51. jac_client/tests/conftest.py +10 -8
  52. jac_client/tests/fixtures/spawn_test/app.jac +15 -18
  53. jac_client/tests/fixtures/with-ts/app.jac +4 -4
  54. jac_client/tests/test_cli.py +99 -45
  55. jac_client/tests/test_it.py +290 -79
  56. {jac_client-0.2.5.dist-info → jac_client-0.2.7.dist-info}/METADATA +16 -7
  57. jac_client-0.2.7.dist-info/RECORD +97 -0
  58. jac_client-0.2.5.dist-info/RECORD +0 -74
  59. {jac_client-0.2.5.dist-info → jac_client-0.2.7.dist-info}/WHEEL +0 -0
  60. {jac_client-0.2.5.dist-info → jac_client-0.2.7.dist-info}/entry_points.txt +0 -0
  61. {jac_client-0.2.5.dist-info → jac_client-0.2.7.dist-info}/top_level.txt +0 -0
jac_client/plugin/cli.jac CHANGED
@@ -1,7 +1,7 @@
1
1
  """Command line interface tool for the Jac Client.
2
2
 
3
3
  This module extends the core `create` command to add client-side (frontend)
4
- project setup via the --cl flag.
4
+ project setup via the --cl flag using the extend_command API.
5
5
  """
6
6
 
7
7
  import os;
@@ -10,112 +10,82 @@ import sys;
10
10
  import pathlib;
11
11
  import subprocess;
12
12
  import shutil;
13
- import from jaclang.cli.cmdreg { cmd_registry, CommandPriority }
13
+ import from jaclang.cli.registry { get_registry }
14
+ import from jaclang.cli.command { Arg, ArgKind, HookContext }
14
15
  import from jaclang.pycore.runtime { hookimpl }
15
- import from jaclang.project.config { JacConfig, find_project_root }
16
+ import from jaclang.project.config { find_project_root }
16
17
 
17
18
  """Jac CLI extensions for client-side development."""
18
19
  class JacCmd {
19
20
  """Create Jac CLI cmds."""
20
21
  @hookimpl
21
22
  static def create_cmd -> None {
22
- """Override core create command to add --cl flag for client-side setup.""";
23
-
24
- def create(
25
- name: str = 'main',
26
- force: bool = False,
27
- cl: bool = False,
28
- skip: bool = False,
29
- verbose: bool = False
30
- ) -> None {
31
- """Initialize a new Jac project.
32
-
33
- Creates a jac.toml configuration file and basic project structure.
34
- Use --cl to include client-side (frontend) setup with Vite bundling.
35
-
36
- Args:
37
- name: Project name (default: 'main')
38
- force: Overwrite existing jac.toml if present
39
- cl: Include client-side (frontend) setup
40
- skip: Skip installing default packages (only for --cl projects)
41
- verbose: Show detailed output during package installation
42
-
43
- Examples:
44
- jac create
45
- jac create myapp
46
- jac create --cl myapp
47
- jac create --cl --skip myapp
48
- jac create --cl --verbose myapp
49
- jac create --force
50
- """;
51
- cwd = pathlib.Path(os.getcwd());
52
-
53
- if cl {
54
- _create_client_project(cwd, name, force, skip, verbose);
55
- } else {
56
- _create_core_project(cwd, name, force);
57
- }
58
- }
59
-
60
- # Register with PLUGIN priority to override core's create command
61
- cmd_registry.register(
62
- create, priority=CommandPriority.PLUGIN, source="jac-client"
23
+ """Extend core create command to add --cl flag for client-side setup.""";
24
+ registry = get_registry();
25
+
26
+ # Extend the core 'create' command with client-specific arguments
27
+ registry.extend_command(
28
+ command_name="create",
29
+ args=[
30
+ Arg.create(
31
+ "cl",
32
+ typ=bool,
33
+ default=False,
34
+ help="Include client-side (frontend) setup",
35
+ short="c"
36
+ ),
37
+ Arg.create(
38
+ "skip",
39
+ typ=bool,
40
+ default=False,
41
+ help="Skip installing default packages (only for --cl)",
42
+ short="s"
43
+ ),
44
+ Arg.create(
45
+ "verbose",
46
+ typ=bool,
47
+ default=False,
48
+ help="Show detailed output during installation",
49
+ short="v"
50
+ ),
51
+
52
+ ],
53
+ pre_hook=_handle_client_create,
54
+ source="jac-client"
63
55
  );
64
56
  }
65
57
  }
66
58
 
67
- """Create a standard Jac project (core behavior)."""
68
- def _create_core_project(cwd: pathlib.Path, name: str, force: bool) -> None {
69
- existing = find_project_root(cwd);
70
- if existing and not force {
71
- (project_root, toml_path) = existing;
72
- print(f"Already in a Jac project: {toml_path}", file=sys.stderr);
73
- print("Use --force to reinitialize.", file=sys.stderr);
74
- <>exit(1);
59
+ """Pre-hook to handle --cl flag for client-side project creation."""
60
+ def _handle_client_create(ctx: HookContext) -> None {
61
+ # Check if --cl flag is set
62
+ cl_flag = ctx.get_arg("cl", False);
63
+ if not cl_flag {
64
+ # Let core create command run normally
65
+ return;
75
66
  }
76
67
 
77
- project_name = name or cwd.name;
78
-
79
- toml_path = cwd / "jac.toml";
80
- if toml_path.exists() and not force {
81
- print("jac.toml already exists. Use --force to overwrite.", file=sys.stderr);
82
- <>exit(1);
83
- }
84
-
85
- toml_content = JacConfig.create_default_toml(project_name);
86
- with open(toml_path, "w") as f {
87
- f.write(toml_content);
88
- }
89
- print(f"Created {toml_path}");
90
-
91
- main_jac = cwd / f"{name}.jac";
92
- if not main_jac.exists() {
93
- with open(main_jac, "w") as f {
94
- f.write(
95
- f'''"""Main entry point for {project_name}."""
96
-
97
- with entry {{
98
- print("Hello from {project_name}!");
99
- }}
100
- '''
101
- );
102
- }
103
- print(f"Created {main_jac}");
104
- }
68
+ # Handle client-side project creation
69
+ cwd = pathlib.Path(os.getcwd());
70
+ name = ctx.get_arg("name", "main");
71
+ force = ctx.get_arg("force", False);
72
+ skip_install = ctx.get_arg("skip", False);
73
+ verbose = ctx.get_arg("verbose", False);
105
74
 
106
- packages_dir = cwd / "packages";
107
- if not packages_dir.exists() {
108
- packages_dir.mkdir(parents=True, exist_ok=True);
109
- print(f"Created {packages_dir}/");
75
+ try {
76
+ _create_client_project(cwd, name, force, skip_install, verbose);
77
+ # Cancel core handler with success code
78
+ ctx.set_data("cancel_execution", True);
79
+ ctx.set_data("cancel_return_code", 0);
80
+ } except SystemExit as e {
81
+ # Preserve exit code from sys.exit() calls
82
+ ctx.set_data("cancel_execution", True);
83
+ ctx.set_data("cancel_return_code", e.code if e.code is not None else 1);
84
+ } except Exception as e {
85
+ print(f"Error creating client project: {e}", file=sys.stderr);
86
+ ctx.set_data("cancel_execution", True);
87
+ ctx.set_data("cancel_return_code", 1);
110
88
  }
111
-
112
- _create_gitignore(cwd, client=False);
113
-
114
- print(f"\nProject '{project_name}' initialized successfully!");
115
- print("\nNext steps:");
116
- print(" jac run main.jac # Run the main entry point");
117
- print(" jac add <package> # Add dependencies");
118
- print(" jac install # Install all dependencies");
119
89
  }
120
90
 
121
91
  """Create a client-side Jac project with organized folder structure."""
@@ -166,23 +136,29 @@ def _create_client_project(
166
136
 
167
137
  print(f"Creating Jac client application: {project_name}");
168
138
 
169
- src_dir = project_path / "src";
170
- src_dir.mkdir(parents=True, exist_ok=True);
171
-
172
- components_dir = src_dir / "components";
139
+ components_dir = project_path / "components";
173
140
  components_dir.mkdir(parents=True, exist_ok=True);
174
141
 
175
142
  assets_dir = project_path / "assets";
176
143
  assets_dir.mkdir(parents=True, exist_ok=True);
177
144
 
178
- (project_path / ".client-build").mkdir(parents=True, exist_ok=True);
145
+ (project_path / ".jac" / "client").mkdir(parents=True, exist_ok=True);
179
146
 
180
147
  toml_path = project_path / "jac.toml";
181
148
  toml_content = f'''[project]
182
149
  name = "{project_name}"
183
150
  version = "1.0.0"
184
151
  description = "Jac client application: {project_name}"
185
- entry-point = "src/app.jac"
152
+ entry-point = "main.jac"
153
+
154
+ [dependencies.npm]
155
+ "jac-client-node" = "1.0.3"
156
+
157
+ [dependencies.npm.dev]
158
+ "@jac-client/dev-deps" = "1.0.0"
159
+
160
+ [serve]
161
+ base_route_app = "app"
186
162
 
187
163
  [plugins.client]
188
164
  # Vite bundler configuration (optional overrides)
@@ -194,16 +170,16 @@ entry-point = "src/app.jac"
194
170
  }
195
171
  print(f"Created {toml_path}");
196
172
 
197
- app_jac_content = '''"""Main entry point for the Jac client application."""
173
+ main_jac_content = '''"""Main entry point for the Jac client application."""
198
174
 
199
- # Client-side imports
200
- cl import from react { useState, useEffect }
201
- cl import from ".components/Button.tsx" { Button }
175
+ # Client-side imports (useState is auto-injected when using `has` variables)
176
+ cl import from react { useEffect }
177
+ cl import from .components.Button { Button }
202
178
 
203
179
  # Client-side component
204
180
  cl {
205
181
  def:pub app() -> any {
206
- [count, setCount] = useState(0);
182
+ has count: int = 0;
207
183
 
208
184
  useEffect(lambda -> None {
209
185
  console.log("Count updated:", count);
@@ -215,12 +191,12 @@ cl {
215
191
  <div style={{display: "flex", gap: "1rem", marginTop: "1rem"}}>
216
192
  <Button
217
193
  label="Increment"
218
- onClick={lambda -> None { setCount(count + 1); }}
194
+ onClick={lambda -> None { count = count + 1; }}
219
195
  variant="primary"
220
196
  />
221
197
  <Button
222
198
  label="Reset"
223
- onClick={lambda -> None { setCount(0); }}
199
+ onClick={lambda -> None { count = 0; }}
224
200
  variant="secondary"
225
201
  />
226
202
  </div>
@@ -228,79 +204,62 @@ cl {
228
204
  }
229
205
  }
230
206
  ''';
231
- with open(src_dir / "app.jac", 'w') as f {
232
- f.write(app_jac_content);
207
+ with open(project_path / "main.jac", 'w') as f {
208
+ f.write(main_jac_content);
233
209
  }
234
- print("Created src/app.jac");
235
-
236
- button_tsx_content = '''import React from 'react';
237
-
238
- interface ButtonProps {
239
- label: string;
240
- onClick?: () => void;
241
- variant?: 'primary' | 'secondary';
242
- disabled?: boolean;
243
- }
210
+ print("Created main.jac");
211
+
212
+ button_cl_jac_content = '''"""Button component for the Jac client application."""
213
+
214
+ def:pub Button(label: str, onClick: any, variant: str = "primary", disabled: bool = False) -> any {
215
+ base_styles = {
216
+ "padding": "0.75rem 1.5rem",
217
+ "fontSize": "1rem",
218
+ "fontWeight": "600",
219
+ "borderRadius": "0.5rem",
220
+ "border": "none",
221
+ "cursor": "not-allowed" if disabled else "pointer",
222
+ "transition": "all 0.2s ease"
223
+ };
224
+
225
+ variant_styles = {
226
+ "primary": {
227
+ "backgroundColor": "#9ca3af" if disabled else "#3b82f6",
228
+ "color": "#ffffff"
229
+ },
230
+ "secondary": {
231
+ "backgroundColor": "#e5e7eb" if disabled else "#6b7280",
232
+ "color": "#ffffff"
233
+ }
234
+ };
244
235
 
245
- export const Button: React.FC<ButtonProps> = ({
246
- label,
247
- onClick,
248
- variant = 'primary',
249
- disabled = false
250
- }) => {
251
- const baseStyles: React.CSSProperties = {
252
- padding: '0.75rem 1.5rem',
253
- fontSize: '1rem',
254
- fontWeight: '600',
255
- borderRadius: '0.5rem',
256
- border: 'none',
257
- cursor: disabled ? 'not-allowed' : 'pointer',
258
- transition: 'all 0.2s ease',
259
- };
260
-
261
- const variantStyles: Record<string, React.CSSProperties> = {
262
- primary: {
263
- backgroundColor: disabled ? '#9ca3af' : '#3b82f6',
264
- color: '#ffffff',
265
- },
266
- secondary: {
267
- backgroundColor: disabled ? '#e5e7eb' : '#6b7280',
268
- color: '#ffffff',
269
- },
270
- };
271
-
272
- return (
273
- <button
274
- style={{ ...baseStyles, ...variantStyles[variant] }}
275
- onClick={onClick}
276
- disabled={disabled}
236
+ return <button
237
+ style={{**base_styles, **variant_styles[variant]}}
238
+ onClick={onClick}
239
+ disabled={disabled}
277
240
  >
278
- {label}
279
- </button>
280
- );
281
- };
282
-
283
- export default Button;
241
+ {label}
242
+ </button>;
243
+ }
284
244
  ''';
285
- with open(components_dir / "Button.tsx", 'w') as f {
286
- f.write(button_tsx_content);
245
+ with open(components_dir / "Button.cl.jac", 'w') as f {
246
+ f.write(button_cl_jac_content);
287
247
  }
288
- print("Created src/components/Button.tsx");
248
+ print("Created components/Button.cl.jac");
289
249
 
290
250
  readme_content = f''' # {project_name}
291
251
 
292
252
 
293
- A Jac client-side application with React and TypeScript support.
253
+ A Jac client-side application with React support.
294
254
 
295
255
  ## Project Structure
296
256
 
297
257
  ```
298
258
  {project_name}/
299
259
  ├── jac.toml # Project configuration
300
- ├── src/ # Source files
301
- ├── app.jac # Main application entry
302
- │ └── components/ # Reusable components
303
- │ └── Button.tsx # Example TypeScript component
260
+ ├── main.jac # Main application entry
261
+ ├── components/ # Reusable components
262
+ │ └── Button.cl.jac # Example Jac component
304
263
  ├── assets/ # Static assets (images, fonts, etc.)
305
264
  └── build/ # Build output (generated)
306
265
  ```
@@ -310,15 +269,15 @@ A Jac client-side application with React and TypeScript support.
310
269
  Start the development server:
311
270
 
312
271
  ```bash
313
- jac serve src/app.jac
272
+ jac start main.jac
314
273
  ```
315
274
 
316
- ## TypeScript Support
275
+ ## Components
317
276
 
318
- Create TypeScript components in `src/components/` and import them in your Jac files:
277
+ Create Jac components in `components/` as `.cl.jac` files and import them:
319
278
 
320
279
  ```jac
321
- cl import from "./components/Button.tsx" {{ Button }}
280
+ cl import from .components.Button {{ Button }}
322
281
  ```
323
282
 
324
283
  ## Adding Dependencies
@@ -334,7 +293,7 @@ jac add --cl react-router-dom
334
293
  }
335
294
  print("Created README.md");
336
295
 
337
- _create_gitignore(project_path, client=True);
296
+ _create_gitignore(project_path);
338
297
 
339
298
  # Install default packages unless --skip is specified
340
299
  if not skip {
@@ -346,15 +305,15 @@ jac add --cl react-router-dom
346
305
  if name and name != cwd.name {
347
306
  print("\nNext steps:");
348
307
  print(f" cd {name}");
349
- print(" jac serve src/app.jac");
308
+ print(" jac start main.jac");
350
309
  } else {
351
310
  print("\nNext steps:");
352
- print(" jac serve src/app.jac");
311
+ print(" jac start main.jac");
353
312
  }
354
313
  }
355
314
 
356
- """Create .gitignore file with appropriate entries."""
357
- def _create_gitignore(project_path: pathlib.Path, client: bool = False) -> None {
315
+ """Create .gitignore file with client-specific entries."""
316
+ def _create_gitignore(project_path: pathlib.Path) -> None {
358
317
  gitignore_entries = [
359
318
  "# Jac project",
360
319
  "packages/",
@@ -372,24 +331,17 @@ def _create_gitignore(project_path: pathlib.Path, client: bool = False) -> None
372
331
  "# IDE",
373
332
  ".idea/",
374
333
  ".vscode/",
375
- "*.swp"
334
+ "*.swp",
335
+ "",
336
+ "# Node.js",
337
+ "node_modules/",
338
+ "",
339
+ "# Jac build artifacts",
340
+ ".jac/",
341
+ "*.session",
342
+ "*.session.*"
376
343
  ];
377
344
 
378
- if client {
379
- gitignore_entries.extend(
380
- [
381
- "",
382
- "# Node.js",
383
- "node_modules/",
384
- "",
385
- "# Jac Client",
386
- ".client-build/",
387
- "*.session",
388
- "*.session.*"
389
- ]
390
- );
391
- }
392
-
393
345
  gitignore_path = project_path / ".gitignore";
394
346
  if gitignore_path.exists() {
395
347
  with open(gitignore_path, "r") as f {
@@ -415,7 +367,7 @@ def _create_gitignore(project_path: pathlib.Path, client: bool = False) -> None
415
367
  }
416
368
  }
417
369
 
418
- """Install default npm packages in .client-build directory."""
370
+ """Install default npm packages in .jac/client directory."""
419
371
  def _install_default_packages(
420
372
  project_path: pathlib.Path, verbose: bool = False
421
373
  ) -> None {
@@ -441,13 +393,13 @@ def _install_default_packages(
441
393
  # Generate package.json with default packages (defaults are added automatically)
442
394
  bundler.create_package_json(project_name=project_name);
443
395
 
444
- # Ensure .client-build directory exists
445
- build_dir = project_path / '.client-build';
446
- build_dir.mkdir(parents=True, exist_ok=True);
396
+ # Ensure .jac/client directory exists
397
+ client_dir = bundler._get_client_dir();
398
+ client_dir.mkdir(parents=True, exist_ok=True);
447
399
 
448
- # Copy package.json to .client-build/ for npm install
449
- configs_package_json = build_dir / '.jac-client.configs' / 'package.json';
450
- build_package_json = build_dir / 'package.json';
400
+ # Copy package.json to .jac/client/ for npm install
401
+ configs_package_json = client_dir / 'configs' / 'package.json';
402
+ build_package_json = client_dir / 'package.json';
451
403
 
452
404
  if not configs_package_json.exists() {
453
405
  print(
@@ -457,7 +409,7 @@ def _install_default_packages(
457
409
  return;
458
410
  }
459
411
 
460
- # Always copy the generated package.json to .client-build/ for npm install
412
+ # Always copy the generated package.json to .jac/client/ for npm install
461
413
  shutil.copy2(configs_package_json, build_package_json);
462
414
 
463
415
  # Read package data for verbose output
@@ -485,27 +437,27 @@ def _install_default_packages(
485
437
  print("\nRunning npm install...");
486
438
  }
487
439
 
488
- # Run npm install in .client-build/ directory
440
+ # Run npm install in .jac/client/ directory
489
441
  try {
490
442
  if verbose {
491
443
  # Stream output for visibility in verbose mode
492
444
  subprocess.run(
493
- ['npm', 'install', '--progress'], cwd=build_dir, check=True
445
+ ['npm', 'install', '--progress'], cwd=client_dir, check=True
494
446
  );
495
447
  } else {
496
448
  # Quiet mode: capture output
497
449
  subprocess.run(
498
450
  ['npm', 'install'],
499
- cwd=build_dir,
451
+ cwd=client_dir,
500
452
  check=True,
501
453
  capture_output=True,
502
454
  text=True
503
455
  );
504
456
  }
505
457
 
506
- # Move package-lock.json to .jac-client.configs/ if it was created
507
- build_package_lock = build_dir / 'package-lock.json';
508
- configs_dir = build_dir / '.jac-client.configs';
458
+ # Move package-lock.json to configs/ if it was created
459
+ build_package_lock = client_dir / 'package-lock.json';
460
+ configs_dir = client_dir / 'configs';
509
461
  configs_package_lock = configs_dir / 'package-lock.json';
510
462
  if build_package_lock.exists() {
511
463
  configs_dir.mkdir(parents=True, exist_ok=True);
@@ -535,7 +487,7 @@ def _install_default_packages(
535
487
  );
536
488
  print("You can install packages later with: jac add --cl", file=sys.stderr);
537
489
  } finally {
538
- # Clean up temporary package.json in .client-build/
490
+ # Clean up temporary package.json in .jac/client/
539
491
  if build_package_json.exists() {
540
492
  build_package_json.unlink();
541
493
  }
@@ -1,6 +1,7 @@
1
1
  """Client-side runtime for Jac JSX and walker interactions."""
2
2
 
3
3
  import from 'react' { * as React }
4
+ import from 'react' { useState as reactUseState }
4
5
  import from 'react-dom/client' { * as ReactDOM }
5
6
  import from 'react-router-dom' {
6
7
  HashRouter as ReactRouterHashRouter,
@@ -15,7 +16,9 @@ import from 'react-router-dom' {
15
16
 
16
17
  def : pub __jacJsx(tag: any, props: dict = {}, children: any = []) -> any;
17
18
 
18
- glob : pub Router = ReactRouterHashRouter,
19
+ # React hooks re-exported for auto-injection by `has` variables
20
+ glob : pub useState = reactUseState,
21
+ Router = ReactRouterHashRouter,
19
22
  Routes = ReactRouterRoutes,
20
23
  Route = ReactRouterRoute,
21
24
  Link = ReactRouterLink,
@@ -36,3 +39,4 @@ def : pub jacIsLoggedIn -> bool;
36
39
  def : pub __getLocalStorage(key: str) -> str;
37
40
  def : pub __setLocalStorage(key: str, value: str) -> None;
38
41
  def : pub __removeLocalStorage(key: str) -> None;
42
+ # React Router components