jac-coder 0.1.0__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.
- jac_coder/__init__.jac +0 -0
- jac_coder/api.jac +82 -0
- jac_coder/cli_entry.py +25 -0
- jac_coder/config.jac +36 -0
- jac_coder/context.jac +17 -0
- jac_coder/data/examples/ai_agent.md +90 -0
- jac_coder/data/examples/blog_app.md +386 -0
- jac_coder/data/examples/core_patterns.md +321 -0
- jac_coder/data/examples/todo_app.md +321 -0
- jac_coder/data/reference/ai.md +131 -0
- jac_coder/data/reference/backend.md +215 -0
- jac_coder/data/reference/frontend.md +271 -0
- jac_coder/data/reference/osp.md +229 -0
- jac_coder/data/reference/pitfalls.md +141 -0
- jac_coder/data/reference/syntax.md +159 -0
- jac_coder/data/rules/core_jac.md +559 -0
- jac_coder/data/rules/fullstack.md +362 -0
- jac_coder/data/rules/workflow.md +88 -0
- jac_coder/events.jac +110 -0
- jac_coder/impl/api.impl.jac +399 -0
- jac_coder/impl/config.impl.jac +163 -0
- jac_coder/impl/context.impl.jac +117 -0
- jac_coder/impl/mcp_manager.impl.jac +380 -0
- jac_coder/impl/memory.impl.jac +247 -0
- jac_coder/impl/nodes.impl.jac +259 -0
- jac_coder/impl/permission.impl.jac +62 -0
- jac_coder/impl/walkers.impl.jac +298 -0
- jac_coder/mcp_manager.jac +35 -0
- jac_coder/memory.jac +15 -0
- jac_coder/nodes.jac +306 -0
- jac_coder/permission.jac +19 -0
- jac_coder/serve_entry.jac +30 -0
- jac_coder/server.jac +324 -0
- jac_coder/tool/__init__.jac +17 -0
- jac_coder/tool/checked.jac +10 -0
- jac_coder/tool/delegation.jac +23 -0
- jac_coder/tool/filesystem.jac +25 -0
- jac_coder/tool/git.jac +18 -0
- jac_coder/tool/guarded.jac +23 -0
- jac_coder/tool/impl/checked.impl.jac +38 -0
- jac_coder/tool/impl/delegation.impl.jac +157 -0
- jac_coder/tool/impl/filesystem.impl.jac +781 -0
- jac_coder/tool/impl/git.impl.jac +115 -0
- jac_coder/tool/impl/guarded.impl.jac +72 -0
- jac_coder/tool/impl/jac_analyzer.impl.jac +593 -0
- jac_coder/tool/impl/jac_docs.impl.jac +136 -0
- jac_coder/tool/impl/jac_tools.impl.jac +79 -0
- jac_coder/tool/impl/mcp.impl.jac +32 -0
- jac_coder/tool/impl/preview.impl.jac +233 -0
- jac_coder/tool/impl/question.impl.jac +29 -0
- jac_coder/tool/impl/scaffold.impl.jac +231 -0
- jac_coder/tool/impl/search.impl.jac +85 -0
- jac_coder/tool/impl/shell.impl.jac +89 -0
- jac_coder/tool/impl/task.impl.jac +12 -0
- jac_coder/tool/impl/think.impl.jac +4 -0
- jac_coder/tool/impl/todo.impl.jac +58 -0
- jac_coder/tool/impl/validate.impl.jac +236 -0
- jac_coder/tool/impl/web.impl.jac +91 -0
- jac_coder/tool/jac_analyzer.jac +21 -0
- jac_coder/tool/jac_docs.jac +9 -0
- jac_coder/tool/jac_tools.jac +11 -0
- jac_coder/tool/mcp.jac +17 -0
- jac_coder/tool/preview.jac +31 -0
- jac_coder/tool/question.jac +7 -0
- jac_coder/tool/scaffold.jac +10 -0
- jac_coder/tool/search.jac +14 -0
- jac_coder/tool/shell.jac +12 -0
- jac_coder/tool/task.jac +9 -0
- jac_coder/tool/think.jac +5 -0
- jac_coder/tool/todo.jac +12 -0
- jac_coder/tool/validate.jac +11 -0
- jac_coder/tool/vision.jac +17 -0
- jac_coder/tool/web.jac +10 -0
- jac_coder/util/__init__.jac +18 -0
- jac_coder/util/colors.jac +20 -0
- jac_coder/util/impl/sandbox.impl.jac +38 -0
- jac_coder/util/impl/tool_output.impl.jac +208 -0
- jac_coder/util/sandbox.jac +8 -0
- jac_coder/util/tool_output.jac +29 -0
- jac_coder/walkers.jac +67 -0
- jac_coder-0.1.0.dist-info/METADATA +9 -0
- jac_coder-0.1.0.dist-info/RECORD +85 -0
- jac_coder-0.1.0.dist-info/WHEEL +5 -0
- jac_coder-0.1.0.dist-info/entry_points.txt +3 -0
- jac_coder-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# AI Integration — by llm()
|
|
2
|
+
|
|
3
|
+
## Required Setup — byllm Import
|
|
4
|
+
|
|
5
|
+
**You MUST import byllm and create a glob model before using any `by llm()` call.**
|
|
6
|
+
|
|
7
|
+
```jac
|
|
8
|
+
import from byllm.lib { Model }
|
|
9
|
+
|
|
10
|
+
glob llm: Model = Model(model_name="gpt-4o");
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Without this, `by llm()` will fail. The glob variable name (e.g. `llm`, `model`) is what you reference in `by llm()` or `by model()`.
|
|
14
|
+
|
|
15
|
+
## Simple LLM Functions
|
|
16
|
+
|
|
17
|
+
The `by llm()` suffix turns a function into an LLM call. The LLM infers behavior from the function name, parameter names, types, and return type.
|
|
18
|
+
|
|
19
|
+
```jac
|
|
20
|
+
import from byllm.lib { Model }
|
|
21
|
+
|
|
22
|
+
glob llm: Model = Model(model_name="gpt-4o");
|
|
23
|
+
|
|
24
|
+
# Simple classification — LLM figures it out from name + types
|
|
25
|
+
def classify_sentiment(text: str) -> str by llm();
|
|
26
|
+
|
|
27
|
+
# With explicit reasoning
|
|
28
|
+
def classify(message: str) -> str by llm(method="Reason", temperature=0.1);
|
|
29
|
+
|
|
30
|
+
# Enum return type for constrained output
|
|
31
|
+
enum Sentiment { POSITIVE, NEGATIVE, NEUTRAL }
|
|
32
|
+
def analyze(text: str) -> Sentiment by llm();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Semantic Annotations
|
|
36
|
+
|
|
37
|
+
`sem` annotations provide system prompts for `by llm()` functions.
|
|
38
|
+
|
|
39
|
+
```jac
|
|
40
|
+
# On methods
|
|
41
|
+
sem MyAgent.respond = """You are an expert coding agent.
|
|
42
|
+
Use tools to read, write, and test code.""";
|
|
43
|
+
|
|
44
|
+
# On fields (describes what the field means to the LLM)
|
|
45
|
+
sem ReviewResult.is_approved = "True if content meets quality standards";
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## ReAct Agent with Tools
|
|
49
|
+
|
|
50
|
+
Passing `tools=` auto-triggers ReAct (reason-act) loop.
|
|
51
|
+
|
|
52
|
+
```jac
|
|
53
|
+
def respond(message: str, chat_history: list[dict]) -> str by llm(
|
|
54
|
+
messages=chat_history,
|
|
55
|
+
tools=[read_file, search, bash_exec],
|
|
56
|
+
incl_info={"reference": knowledge_base},
|
|
57
|
+
max_react_iterations=10,
|
|
58
|
+
temperature=0.2,
|
|
59
|
+
stream=True
|
|
60
|
+
);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## LLM-Driven Graph Traversal
|
|
64
|
+
|
|
65
|
+
The LLM can decide which node to visit next:
|
|
66
|
+
|
|
67
|
+
```jac
|
|
68
|
+
# LLM picks child node based on context
|
|
69
|
+
can route with Router entry {
|
|
70
|
+
visit [-->] by llm(
|
|
71
|
+
incl_info={"message": self.message, "context": self.context}
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Model Configuration
|
|
77
|
+
|
|
78
|
+
```jac
|
|
79
|
+
import from byllm.lib { Model }
|
|
80
|
+
|
|
81
|
+
glob model: Model = Model(model_name="openai/gpt-4o-mini");
|
|
82
|
+
|
|
83
|
+
# Use specific model for a function
|
|
84
|
+
def summarize(text: str) -> str by model(
|
|
85
|
+
reason="Summarize in 2-3 sentences"
|
|
86
|
+
);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Interface/Implementation with LLM
|
|
90
|
+
|
|
91
|
+
Declaration file:
|
|
92
|
+
```jac
|
|
93
|
+
node Router {
|
|
94
|
+
def classify(message: str) -> str by llm(method="Reason");
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Implementation file:
|
|
99
|
+
```jac
|
|
100
|
+
sem Router.classify = """Return exactly one label: BUILD, PLAN, or EXPLORE.""";
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Full Agent Example
|
|
104
|
+
|
|
105
|
+
```jac
|
|
106
|
+
node Router {}
|
|
107
|
+
node QAHandler {
|
|
108
|
+
def respond(message: str, context: list[dict]) -> str by llm(
|
|
109
|
+
method="Reason", messages=context, temperature=0.3
|
|
110
|
+
);
|
|
111
|
+
can handle with process_request entry;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
walker :pub process_request {
|
|
115
|
+
has message: str;
|
|
116
|
+
has context: list[dict] = [];
|
|
117
|
+
|
|
118
|
+
can route with Router entry {
|
|
119
|
+
visit [-->] by llm(
|
|
120
|
+
incl_info={"message": self.message}
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
can init_graph with Root entry {
|
|
124
|
+
visit [-->][?:Router] else {
|
|
125
|
+
router = (here ++> Router())[0];
|
|
126
|
+
router ++> QAHandler();
|
|
127
|
+
visit router;
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Backend Development — Endpoints, Persistence, Auth
|
|
2
|
+
|
|
3
|
+
## Function Endpoints (def:pub / def:priv)
|
|
4
|
+
|
|
5
|
+
Simple CRUD — preferred for most use cases.
|
|
6
|
+
|
|
7
|
+
```jac
|
|
8
|
+
import from uuid { uuid4 }
|
|
9
|
+
import from datetime { datetime }
|
|
10
|
+
|
|
11
|
+
node Todo {
|
|
12
|
+
has id: str = "";
|
|
13
|
+
has title: str = "";
|
|
14
|
+
has completed: bool = False;
|
|
15
|
+
has created_at: str = "";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def:pub get_todos() -> list {
|
|
19
|
+
return [{"id": t.id, "title": t.title, "completed": t.completed}
|
|
20
|
+
for t in [root-->][?:Todo]];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def:pub add_todo(title: str) -> dict {
|
|
24
|
+
todo = (root ++> Todo(
|
|
25
|
+
id=str(uuid4()), title=title,
|
|
26
|
+
created_at=str(datetime.now())
|
|
27
|
+
))[0];
|
|
28
|
+
return {"id": todo.id, "title": todo.title, "completed": False};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def:pub toggle_todo(id: str) -> dict {
|
|
32
|
+
for todo in [root-->][?:Todo] {
|
|
33
|
+
if todo.id == id {
|
|
34
|
+
todo.completed = not todo.completed;
|
|
35
|
+
return {"id": todo.id, "completed": todo.completed};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {"error": "not found"};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def:pub delete_todo(id: str) -> dict {
|
|
42
|
+
for todo in [root-->][?:Todo] {
|
|
43
|
+
if todo.id == id {
|
|
44
|
+
del todo;
|
|
45
|
+
return {"deleted": id};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {"error": "not found"};
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Walker Endpoints
|
|
53
|
+
|
|
54
|
+
For graph traversal and multi-step operations:
|
|
55
|
+
|
|
56
|
+
```jac
|
|
57
|
+
walker :pub get_post_with_comments {
|
|
58
|
+
has post_id: str = "";
|
|
59
|
+
|
|
60
|
+
can find_post with Root entry {
|
|
61
|
+
for p in [-->][?:Post] {
|
|
62
|
+
if p.id == self.post_id { visit p; return; }
|
|
63
|
+
}
|
|
64
|
+
report {"error": "not found"};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
can collect with Post entry {
|
|
68
|
+
comments = [{"id": c.id, "text": c.text}
|
|
69
|
+
for c in [-->][?:Comment]];
|
|
70
|
+
report {
|
|
71
|
+
"post": {"id": here.id, "title": here.title},
|
|
72
|
+
"comments": comments
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Data Persistence
|
|
79
|
+
|
|
80
|
+
- Nodes connected to `root` **auto-persist** across requests (SQLite)
|
|
81
|
+
- No database setup needed — the language handles it
|
|
82
|
+
- `del node;` removes from graph and storage
|
|
83
|
+
|
|
84
|
+
```jac
|
|
85
|
+
# Create + persist
|
|
86
|
+
root ++> Todo(id=str(uuid4()), title="Buy milk");
|
|
87
|
+
|
|
88
|
+
# Query all
|
|
89
|
+
todos = [root-->][?:Todo];
|
|
90
|
+
|
|
91
|
+
# Query with filter
|
|
92
|
+
found = [root-->][?:Todo](?title == "Buy milk");
|
|
93
|
+
|
|
94
|
+
# Delete
|
|
95
|
+
root del--> todo;
|
|
96
|
+
# or: del todo;
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Per-User Data Isolation (def:priv)
|
|
100
|
+
|
|
101
|
+
`:priv` endpoints require authentication. Each user gets their own isolated `root`.
|
|
102
|
+
|
|
103
|
+
```jac
|
|
104
|
+
# Public — shared root, anyone can call
|
|
105
|
+
def:pub get_shared_data() -> list {
|
|
106
|
+
return [{"id": str(d.id)} for d in [root-->][?:DataNode]];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Private — per-user root, requires login
|
|
110
|
+
def:priv get_my_tasks() -> list {
|
|
111
|
+
return [{"id": t.id, "title": t.title}
|
|
112
|
+
for t in [root-->][?:Task]];
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Authentication
|
|
117
|
+
|
|
118
|
+
Built-in auth functions for fullstack apps:
|
|
119
|
+
|
|
120
|
+
```jac
|
|
121
|
+
cl import from "@jac/runtime" { jacLogin, jacSignup, jacLogout, jacIsLoggedIn }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
| Function | Returns | Purpose |
|
|
125
|
+
|----------|---------|---------|
|
|
126
|
+
| `jacLogin(username, password)` | `bool` | Log in, stores token |
|
|
127
|
+
| `jacSignup(username, password)` | `dict` (has `.success`) | Register user |
|
|
128
|
+
| `jacLogout()` | `void` | Clear auth token |
|
|
129
|
+
| `jacIsLoggedIn()` | `bool` | Check valid token |
|
|
130
|
+
|
|
131
|
+
## File-Based Routing
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
pages/
|
|
135
|
+
├── layout.jac # Root layout with <Outlet />
|
|
136
|
+
├── index.jac # Route: /
|
|
137
|
+
├── about.jac # Route: /about
|
|
138
|
+
├── users/
|
|
139
|
+
│ ├── index.jac # Route: /users
|
|
140
|
+
│ └── [id].jac # Route: /users/:id (dynamic)
|
|
141
|
+
├── (public)/ # Route group (no URL segment)
|
|
142
|
+
│ └── login.jac # Route: /login
|
|
143
|
+
├── (auth)/ # Protected group
|
|
144
|
+
│ ├── layout.jac # AuthGuard layout
|
|
145
|
+
│ └── dashboard.jac # Route: /dashboard
|
|
146
|
+
└── [...notFound].jac # Catch-all 404
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Layout with Outlet
|
|
150
|
+
|
|
151
|
+
```jac
|
|
152
|
+
# pages/layout.jac
|
|
153
|
+
cl import from "@jac/runtime" { Outlet, Link }
|
|
154
|
+
|
|
155
|
+
cl {
|
|
156
|
+
def:pub layout() -> JsxElement {
|
|
157
|
+
return <>
|
|
158
|
+
<nav>
|
|
159
|
+
<Link to="/">Home</Link>
|
|
160
|
+
<Link to="/about">About</Link>
|
|
161
|
+
</nav>
|
|
162
|
+
<main><Outlet /></main>
|
|
163
|
+
</>;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Dynamic Routes
|
|
169
|
+
|
|
170
|
+
```jac
|
|
171
|
+
# pages/users/[id].jac
|
|
172
|
+
cl import from "@jac/runtime" { useParams }
|
|
173
|
+
|
|
174
|
+
cl {
|
|
175
|
+
def:pub page() -> JsxElement {
|
|
176
|
+
params = useParams();
|
|
177
|
+
return <div>User: {params.id}</div>;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Protected Routes
|
|
183
|
+
|
|
184
|
+
```jac
|
|
185
|
+
# pages/(auth)/layout.jac
|
|
186
|
+
cl import from "@jac/runtime" { AuthGuard, Outlet }
|
|
187
|
+
|
|
188
|
+
cl {
|
|
189
|
+
def:pub layout() -> JsxElement {
|
|
190
|
+
return <AuthGuard redirect="/login"><Outlet /></AuthGuard>;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Navigation
|
|
196
|
+
|
|
197
|
+
```jac
|
|
198
|
+
cl import from "@jac/runtime" { useNavigate, Link }
|
|
199
|
+
|
|
200
|
+
navigate = useNavigate();
|
|
201
|
+
navigate("/path"); # Push
|
|
202
|
+
navigate("/path", {"replace": True}); # Replace
|
|
203
|
+
navigate(-1); # Back
|
|
204
|
+
|
|
205
|
+
<Link to="/about">About</Link>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Running a Fullstack App
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
jac start --dev # Dev: Vite on :8000, API on :8001
|
|
212
|
+
jac start # Production: single server on :8000
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Visit `http://localhost:8000/docs` for Swagger UI.
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# Frontend Components — .cl.jac
|
|
2
|
+
|
|
3
|
+
## Component Definition
|
|
4
|
+
|
|
5
|
+
Components are public functions returning `JsxElement`.
|
|
6
|
+
|
|
7
|
+
```jac
|
|
8
|
+
def:pub Header(props: dict) -> JsxElement {
|
|
9
|
+
title = props.title or "";
|
|
10
|
+
return (
|
|
11
|
+
<header className="border-b px-6 py-4">
|
|
12
|
+
<h1 className="text-xl font-bold">{title}</h1>
|
|
13
|
+
</header>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Components need `def:pub` to be importable by other files.
|
|
19
|
+
|
|
20
|
+
## State Management
|
|
21
|
+
|
|
22
|
+
`has` inside a component function auto-generates `useState`.
|
|
23
|
+
|
|
24
|
+
```jac
|
|
25
|
+
def:pub Counter() -> JsxElement {
|
|
26
|
+
has count: int = 0;
|
|
27
|
+
has name: str = "";
|
|
28
|
+
has items: list = [];
|
|
29
|
+
|
|
30
|
+
# Assignment triggers re-render (calls setter internally)
|
|
31
|
+
count = count + 1;
|
|
32
|
+
items = items + [newItem]; # new reference = re-render
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- NEVER define `setX` yourself — it already exists from `has`
|
|
37
|
+
- NEVER use `.append()` — it mutates in place (no re-render)
|
|
38
|
+
- Always init with correct type, NEVER `None` or `any`
|
|
39
|
+
|
|
40
|
+
## Effects (Lifecycle)
|
|
41
|
+
|
|
42
|
+
```jac
|
|
43
|
+
can with entry { ... } # Mount (useEffect with [])
|
|
44
|
+
async can with entry { ... } # Async mount
|
|
45
|
+
can with [dep1, dep2] entry { ... } # Watch dependencies
|
|
46
|
+
can with (a, b) entry { ... } # Also dependency watch
|
|
47
|
+
can with exit { ... } # Cleanup/unmount
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
NEVER use `useEffect(lambda...)` — that is OLD syntax.
|
|
51
|
+
|
|
52
|
+
## Event Handlers
|
|
53
|
+
|
|
54
|
+
ALL handlers must be named `def` functions defined BEFORE `return`. NEVER use lambda or inline def in JSX.
|
|
55
|
+
|
|
56
|
+
```jac
|
|
57
|
+
def:pub TodoList() -> JsxElement {
|
|
58
|
+
has inputValue: str = "";
|
|
59
|
+
|
|
60
|
+
# Define handler above return
|
|
61
|
+
def handle_input(e: any) -> None {
|
|
62
|
+
inputValue = e.target.value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
def handle_submit() -> None {
|
|
66
|
+
if inputValue { add_item(inputValue); inputValue = ""; }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Pass by name
|
|
70
|
+
return <div>
|
|
71
|
+
<input value={inputValue} onChange={handle_input} />
|
|
72
|
+
<button onClick={handle_submit}>Add</button>
|
|
73
|
+
</div>;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## List Rendering
|
|
78
|
+
|
|
79
|
+
Use list comprehension, NEVER `.map()`.
|
|
80
|
+
|
|
81
|
+
```jac
|
|
82
|
+
# Render list
|
|
83
|
+
{[<TodoItem key={item["id"]} data={item} /> for item in items]}
|
|
84
|
+
|
|
85
|
+
# With filter
|
|
86
|
+
{[<Tag key={t} label={t} /> for t in tags if t != "hidden"]}
|
|
87
|
+
|
|
88
|
+
# Add to list (new reference)
|
|
89
|
+
items = items + [newItem];
|
|
90
|
+
|
|
91
|
+
# Remove from list
|
|
92
|
+
items = [item for item in items if item["id"] != targetId];
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Conditional Rendering
|
|
96
|
+
|
|
97
|
+
```jac
|
|
98
|
+
{showSidebar and (<Sidebar items={items} />)}
|
|
99
|
+
{isActive and (<ActiveView />) or (<InactiveView />)}
|
|
100
|
+
{(not loading) and (<Content />)}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Custom Hooks
|
|
104
|
+
|
|
105
|
+
```jac
|
|
106
|
+
def:pub useCounter(initial: int = 0) -> dict {
|
|
107
|
+
has count: int = initial;
|
|
108
|
+
def increment() -> None { count = count + 1; }
|
|
109
|
+
def decrement() -> None { count = count - 1; }
|
|
110
|
+
return {"count": count, "increment": increment, "decrement": decrement};
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Data Hook Pattern (sv import + state)
|
|
115
|
+
|
|
116
|
+
```jac
|
|
117
|
+
# hooks/useTodos.cl.jac
|
|
118
|
+
sv import from ..main { get_todos, add_todo, delete_todo }
|
|
119
|
+
|
|
120
|
+
def:pub useTodos() -> dict {
|
|
121
|
+
has todos: list = [];
|
|
122
|
+
has loading: bool = True;
|
|
123
|
+
has error: str = "";
|
|
124
|
+
|
|
125
|
+
async can with entry {
|
|
126
|
+
try {
|
|
127
|
+
result = await get_todos();
|
|
128
|
+
todos = result or [];
|
|
129
|
+
} except Exception as e {
|
|
130
|
+
error = str(e);
|
|
131
|
+
}
|
|
132
|
+
loading = False;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async def handleAdd(title: str) -> None {
|
|
136
|
+
if not title { return; }
|
|
137
|
+
result = await add_todo(title); # positional args only
|
|
138
|
+
if result and not result.error {
|
|
139
|
+
todos = todos + [result];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async def handleDelete(id: str) -> None {
|
|
144
|
+
result = await delete_todo(id); # positional args only
|
|
145
|
+
if result and not result.error {
|
|
146
|
+
todos = [t for t in todos if t["id"] != id];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
"todos": todos, "loading": loading, "error": error,
|
|
152
|
+
"handleAdd": handleAdd, "handleDelete": handleDelete
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Frontend Import Rules
|
|
158
|
+
|
|
159
|
+
```jac
|
|
160
|
+
# Same directory (.cl.jac to .cl.jac) — WITH quotes, dots not slashes
|
|
161
|
+
import from ".Header" { Header }
|
|
162
|
+
|
|
163
|
+
# Parent directory — WITH quotes
|
|
164
|
+
import from "..hooks.useTodos" { useTodos }
|
|
165
|
+
|
|
166
|
+
# Two levels up — WITH quotes
|
|
167
|
+
import from "...lib.utils" { cn }
|
|
168
|
+
|
|
169
|
+
# Server calls — sv import, NO quotes
|
|
170
|
+
sv import from ..main { get_todos, add_todo }
|
|
171
|
+
|
|
172
|
+
# NPM packages — WITH quotes
|
|
173
|
+
import from "clsx" { cn }
|
|
174
|
+
|
|
175
|
+
# Runtime — cl import, WITH quotes
|
|
176
|
+
cl import from "@jac/runtime" { jacLogin, Outlet }
|
|
177
|
+
|
|
178
|
+
# CSS — WITH quotes
|
|
179
|
+
import "..styles.global.css";
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Dot levels:** `.` = same dir, `..` = parent, `...` = 2 up
|
|
183
|
+
|
|
184
|
+
## Calling Backend Walkers
|
|
185
|
+
|
|
186
|
+
```jac
|
|
187
|
+
sv import from ..main { get_post_details }
|
|
188
|
+
|
|
189
|
+
async def loadDetails() -> None {
|
|
190
|
+
result = root spawn get_post_details(post_id=postId);
|
|
191
|
+
if result and result.reports and len(result.reports) > 0 {
|
|
192
|
+
data = result.reports[0];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Primitive Idioms (Jac, NOT JavaScript)
|
|
198
|
+
|
|
199
|
+
| JS (WRONG) | Jac (RIGHT) |
|
|
200
|
+
|---|---|
|
|
201
|
+
| `console.log(x)` | `print(x)` |
|
|
202
|
+
| `String(x)` / `.toString()` | `str(x)` |
|
|
203
|
+
| `parseInt(x)` | `int(x)` |
|
|
204
|
+
| `x.length` | `len(x)` |
|
|
205
|
+
| `.toLowerCase()` | `.lower()` |
|
|
206
|
+
| `.toUpperCase()` | `.upper()` |
|
|
207
|
+
| `.trim()` | `.strip()` |
|
|
208
|
+
| `.indexOf(sub)` | `.find(sub)` |
|
|
209
|
+
|
|
210
|
+
## Inline Styles
|
|
211
|
+
|
|
212
|
+
```jac
|
|
213
|
+
<div style={{"display": "flex", "gap": "8px", "backgroundColor": "#f0f0f0"}} />
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Service Layer Pattern (for walker calls)
|
|
217
|
+
|
|
218
|
+
```jac
|
|
219
|
+
# services/apiService.cl.jac
|
|
220
|
+
sv import from ..main { create_item, list_items }
|
|
221
|
+
|
|
222
|
+
async def:pub createItem(title: str) -> any {
|
|
223
|
+
try {
|
|
224
|
+
response = root spawn create_item(title=title);
|
|
225
|
+
result = response.reports[len(response.reports) - 1]
|
|
226
|
+
if response.reports and len(response.reports) > 0 else {};
|
|
227
|
+
return {"success": True, "item": result};
|
|
228
|
+
} except Exception as e {
|
|
229
|
+
return {"success": False, "error": str(e)};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## @jac/runtime — Available Symbols
|
|
235
|
+
|
|
236
|
+
Use `cl import from "@jac/runtime"` (WITH quotes, WITH `cl` prefix) to import runtime utilities.
|
|
237
|
+
|
|
238
|
+
### Auth
|
|
239
|
+
```jac
|
|
240
|
+
cl import from "@jac/runtime" { jacLogin, jacSignup, jacLogout, jacIsLoggedIn }
|
|
241
|
+
|
|
242
|
+
# Usage
|
|
243
|
+
success = await jacLogin(username, password); # positional args only
|
|
244
|
+
success = await jacSignup(username, password);
|
|
245
|
+
jacLogout();
|
|
246
|
+
is_auth = jacIsLoggedIn(); # returns bool
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Routing
|
|
250
|
+
```jac
|
|
251
|
+
cl import from "@jac/runtime" { Link, Navigate, useNavigate, Outlet }
|
|
252
|
+
|
|
253
|
+
# Link — declarative navigation
|
|
254
|
+
<Link to="/dashboard">Go to Dashboard</Link>
|
|
255
|
+
|
|
256
|
+
# Navigate — redirect component
|
|
257
|
+
if not jacIsLoggedIn() { return <Navigate to="/login" />; }
|
|
258
|
+
|
|
259
|
+
# useNavigate — programmatic navigation
|
|
260
|
+
navigate = useNavigate();
|
|
261
|
+
def handle_go() -> None { navigate("/dashboard"); }
|
|
262
|
+
|
|
263
|
+
# Outlet — renders child routes (file-based routing)
|
|
264
|
+
return <div><Outlet /></div>;
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### IMPORTANT
|
|
268
|
+
- ALWAYS use `cl import` prefix (not plain `import`)
|
|
269
|
+
- ALWAYS use quotes: `"@jac/runtime"` (it's an npm-style path)
|
|
270
|
+
- NEVER use dots: `@jac.runtime` ← WRONG
|
|
271
|
+
- Auth functions use POSITIONAL args (kwargs broken in .cl.jac)
|