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
@@ -1,6 +1,4 @@
1
1
  # Todo App
2
- cl import from react { useEffect }
3
-
4
2
  node Todo {
5
3
  has text: str,
6
4
  done: bool = False;
@@ -11,14 +9,14 @@ walker create_todo {
11
9
 
12
10
  can create with `root entry {
13
11
  new_todo = here ++> Todo(text=self.text);
14
- report new_todo ;
12
+ report new_todo;
15
13
  }
16
14
  }
17
15
 
18
16
  walker toggle_todo {
19
17
  can toggle with Todo entry {
20
18
  here.done = not here.done;
21
- report here ;
19
+ report here;
22
20
  }
23
21
  }
24
22
 
@@ -28,23 +26,20 @@ walker read_todos {
28
26
  }
29
27
 
30
28
  can report_todos with exit {
31
- report here ;
29
+ report here;
32
30
  }
33
31
  }
34
32
 
35
33
  cl {
36
34
  def:pub app -> any {
37
- has todos: list = [];
38
- has inputValue: str = "";
39
- has filter: str = "all";
35
+ has todos: list = [],
36
+ inputValue: str = "",
37
+ filter: str = "all";
40
38
 
41
- useEffect(
42
- lambda -> None{ async def loadTodos -> None {
43
- result = root spawn read_todos();
44
- todos = result.reports;
45
- } loadTodos();} ,
46
- []
47
- );
39
+ async can with entry {
40
+ result = root spawn read_todos();
41
+ todos = result.reports;
42
+ }
48
43
 
49
44
  # Add a new todo
50
45
  async def addTodo -> None {
@@ -63,7 +58,7 @@ cl {
63
58
  console.log("toggleTodo", id);
64
59
  id spawn toggle_todo();
65
60
  todos = todos.map(
66
- lambda todo: any -> any{ if todo._jac_id == id {
61
+ lambda todo: any -> any { if todo._jac_id == id {
67
62
  updatedTodo = {
68
63
  "_jac_id": todo._jac_id,
69
64
  "text": todo.text,
@@ -77,283 +72,291 @@ cl {
77
72
 
78
73
  # Delete a todo
79
74
  def deleteTodo(id: any) -> None {
80
- todos = todos.filter(lambda todo: any -> bool{ return todo.id != id; } );
75
+ todos = todos.filter(lambda todo: any -> bool { return todo.id != id; });
81
76
  }
82
77
 
83
78
  # Clear all completed todos
84
79
  def clearCompleted -> None {
85
- todos = todos.filter(lambda todo: any -> bool{ return not todo.done; } );
80
+ todos = todos.filter(lambda todo: any -> bool { return not todo.done; });
86
81
  }
87
82
 
88
83
  # Filter todos based on current filter
89
84
  def getFilteredTodos -> list {
90
85
  if filter == "active" {
91
86
  return todos.filter(
92
- lambda todo: any -> bool{ return not todo.done; }
87
+ lambda todo: any -> bool { return not todo.done; }
93
88
  );
94
89
  } elif filter == "completed" {
95
- return todos.filter(lambda todo: any -> bool{ return todo.done; } );
90
+ return todos.filter(lambda todo: any -> bool { return todo.done; });
96
91
  }
97
92
  return todos;
98
93
  }
99
94
 
100
95
  # Count active todos
101
- activeCount = todos.filter(
102
- lambda todo: any -> bool{ return not todo.done; }
103
- ).length;
96
+ activeCount = todos.filter(lambda todo: any -> bool { return not todo.done; }).length;
104
97
 
105
98
  filteredTodos = getFilteredTodos();
106
99
 
107
- return <div
108
- style={{
109
- "maxWidth": "600px",
110
- "margin": "40px auto",
111
- "padding": "24px",
112
- "fontFamily": "system-ui, -apple-system, sans-serif",
113
- "background": "#f9fafb",
114
- "minHeight": "100vh"
115
- }}
116
- >
117
- <h1
118
- style={{
119
- "textAlign": "center",
120
- "color": "#1f2937",
121
- "marginBottom": "32px",
122
- "fontSize": "2.5rem",
123
- "fontWeight": "700"
124
- }}
125
- >
126
- 📝 My Todo App
127
- </h1>
128
- # Add todo form
100
+ return
129
101
  <div
130
102
  style={{
131
- "display": "flex",
132
- "gap": "8px",
133
- "marginBottom": "24px",
134
- "background": "#ffffff",
135
- "padding": "16px",
136
- "borderRadius": "12px",
137
- "boxShadow": "0 1px 3px rgba(0,0,0,0.1)"
103
+ "maxWidth": "600px",
104
+ "margin": "40px auto",
105
+ "padding": "24px",
106
+ "fontFamily": "system-ui, -apple-system, sans-serif",
107
+ "background": "#f9fafb",
108
+ "minHeight": "100vh"
138
109
  }}
139
110
  >
140
- <input
141
- type="text"
142
- value={inputValue}
143
- onChange={lambda e: any -> None{ inputValue = e.target.value;} }
144
- onKeyPress={lambda e: any -> None{ if e.key == "Enter" {
145
- addTodo();
146
- }} }
147
- placeholder="What needs to be done?"
111
+ <h1
148
112
  style={{
149
- "flex": "1",
150
- "padding": "12px 16px",
151
- "border": "1px solid #e5e7eb",
152
- "borderRadius": "8px",
153
- "fontSize": "16px",
154
- "outline": "none"
155
- }}
156
- />
157
- <button
158
- onClick={addTodo}
159
- style={{
160
- "padding": "12px 24px",
161
- "background": "#3b82f6",
162
- "color": "#ffffff",
163
- "border": "none",
164
- "borderRadius": "8px",
165
- "fontSize": "16px",
166
- "fontWeight": "600",
167
- "cursor": "pointer",
168
- "transition": "background 0.2s"
113
+ "textAlign": "center",
114
+ "color": "#1f2937",
115
+ "marginBottom": "32px",
116
+ "fontSize": "2.5rem",
117
+ "fontWeight": "700"
169
118
  }}
170
119
  >
171
- Add
172
- </button>
173
- </div>
174
- # Filter buttons
175
- <div
176
- style={{
177
- "display": "flex",
178
- "gap": "8px",
179
- "marginBottom": "24px",
180
- "justifyContent": "center"
181
- }}
182
- >
183
- <button
184
- onClick={lambda -> None{ filter = "all";} }
120
+ 📝 My Todo App
121
+ </h1>
122
+ # Add todo form
123
+ <div
185
124
  style={{
186
- "padding": "8px 16px",
187
- "background": ("#3b82f6" if filter == "all" else "#ffffff"),
188
- "color": ("#ffffff" if filter == "all" else "#3b82f6"),
189
- "border": "1px solid #3b82f6",
190
- "borderRadius": "6px",
191
- "fontSize": "14px",
192
- "fontWeight": "600",
193
- "cursor": "pointer"
125
+ "display": "flex",
126
+ "gap": "8px",
127
+ "marginBottom": "24px",
128
+ "background": "#ffffff",
129
+ "padding": "16px",
130
+ "borderRadius": "12px",
131
+ "boxShadow": "0 1px 3px rgba(0,0,0,0.1)"
194
132
  }}
195
133
  >
196
- All
197
- </button>
198
- <button
199
- onClick={lambda -> None{ filter = "active";} }
134
+ <input
135
+ type="text"
136
+ value={inputValue}
137
+ onChange={lambda e: any -> None { inputValue = e.target.value;}}
138
+ onKeyPress={lambda e: any -> None { if e.key == "Enter" {
139
+ addTodo();
140
+ }}}
141
+ placeholder="What needs to be done?"
142
+ style={{
143
+ "flex": "1",
144
+ "padding": "12px 16px",
145
+ "border": "1px solid #e5e7eb",
146
+ "borderRadius": "8px",
147
+ "fontSize": "16px",
148
+ "outline": "none"
149
+ }}
150
+ />
151
+ <button
152
+ onClick={addTodo}
153
+ style={{
154
+ "padding": "12px 24px",
155
+ "background": "#3b82f6",
156
+ "color": "#ffffff",
157
+ "border": "none",
158
+ "borderRadius": "8px",
159
+ "fontSize": "16px",
160
+ "fontWeight": "600",
161
+ "cursor": "pointer",
162
+ "transition": "background 0.2s"
163
+ }}
164
+ >
165
+ Add
166
+ </button>
167
+ </div>
168
+ # Filter buttons
169
+ <div
200
170
  style={{
201
- "padding": "8px 16px",
202
- "background": ("#3b82f6" if filter == "active" else "#ffffff"),
203
- "color": ("#ffffff" if filter == "active" else "#3b82f6"),
204
- "border": "1px solid #3b82f6",
205
- "borderRadius": "6px",
206
- "fontSize": "14px",
207
- "fontWeight": "600",
208
- "cursor": "pointer"
171
+ "display": "flex",
172
+ "gap": "8px",
173
+ "marginBottom": "24px",
174
+ "justifyContent": "center"
209
175
  }}
210
176
  >
211
- Active
212
- </button>
213
- <button
214
- onClick={lambda -> None{ filter = "completed";} }
177
+ <button
178
+ onClick={lambda -> None { filter = "all";}}
179
+ style={{
180
+ "padding": "8px 16px",
181
+ "background": ("#3b82f6" if filter == "all" else "#ffffff"),
182
+ "color": ("#ffffff" if filter == "all" else "#3b82f6"),
183
+ "border": "1px solid #3b82f6",
184
+ "borderRadius": "6px",
185
+ "fontSize": "14px",
186
+ "fontWeight": "600",
187
+ "cursor": "pointer"
188
+ }}
189
+ >
190
+ All
191
+ </button>
192
+ <button
193
+ onClick={lambda -> None { filter = "active";}}
194
+ style={{
195
+ "padding": "8px 16px",
196
+ "background": (
197
+ "#3b82f6" if filter == "active" else "#ffffff"
198
+ ),
199
+ "color": ("#ffffff" if filter == "active" else "#3b82f6"),
200
+ "border": "1px solid #3b82f6",
201
+ "borderRadius": "6px",
202
+ "fontSize": "14px",
203
+ "fontWeight": "600",
204
+ "cursor": "pointer"
205
+ }}
206
+ >
207
+ Active
208
+ </button>
209
+ <button
210
+ onClick={lambda -> None { filter = "completed";}}
211
+ style={{
212
+ "padding": "8px 16px",
213
+ "background": (
214
+ "#3b82f6" if filter == "completed" else "#ffffff"
215
+ ),
216
+ "color": (
217
+ "#ffffff" if filter == "completed" else "#3b82f6"
218
+ ),
219
+ "border": "1px solid #3b82f6",
220
+ "borderRadius": "6px",
221
+ "fontSize": "14px",
222
+ "fontWeight": "600",
223
+ "cursor": "pointer"
224
+ }}
225
+ >
226
+ Completed
227
+ </button>
228
+ </div>
229
+ # Todo list
230
+ <div
215
231
  style={{
216
- "padding": "8px 16px",
217
- "background": (
218
- "#3b82f6" if filter == "completed" else "#ffffff"
219
- ),
220
- "color": ("#ffffff" if filter == "completed" else "#3b82f6"),
221
- "border": "1px solid #3b82f6",
222
- "borderRadius": "6px",
223
- "fontSize": "14px",
224
- "fontWeight": "600",
225
- "cursor": "pointer"
232
+ "background": "#ffffff",
233
+ "borderRadius": "12px",
234
+ "boxShadow": "0 1px 3px rgba(0,0,0,0.1)",
235
+ "overflow": "hidden"
226
236
  }}
227
237
  >
228
- Completed
229
- </button>
230
- </div>
231
- # Todo list
232
- <div
233
- style={{
234
- "background": "#ffffff",
235
- "borderRadius": "12px",
236
- "boxShadow": "0 1px 3px rgba(0,0,0,0.1)",
237
- "overflow": "hidden"
238
- }}
239
- >
238
+ {(
239
+ <div
240
+ style={{
241
+ "padding": "40px",
242
+ "textAlign": "center",
243
+ "color": "#9ca3af"
244
+ }}
245
+ >
246
+ {(
247
+ "No todos yet. Add one above!"
248
+ if filter == "all"
249
+ else (
250
+ "No active todos!"
251
+ if filter == "active"
252
+ else "No completed todos!"
253
+ )
254
+ )}
255
+ </div>
256
+ )
257
+ if filteredTodos.length == 0
258
+ else (
259
+ filteredTodos.map(
260
+ lambda todo: any -> any { return
261
+ <div
262
+ key={todo._jac_id}
263
+ style={{
264
+ "display": "flex",
265
+ "alignItems": "center",
266
+ "gap": "12px",
267
+ "padding": "16px",
268
+ "borderBottom": "1px solid #e5e7eb",
269
+ "transition": "background 0.2s"
270
+ }}
271
+ >
272
+ <input
273
+ type="checkbox"
274
+ checked={todo.done}
275
+ onChange={lambda -> None { toggleTodo(
276
+ todo._jac_id
277
+ );}}
278
+ style={{
279
+ "width": "20px",
280
+ "height": "20px",
281
+ "cursor": "pointer"
282
+ }}
283
+ />
284
+ <span
285
+ style={{
286
+ "flex": "1",
287
+ "textDecoration": (
288
+ "line-through" if todo.done else "none"
289
+ ),
290
+ "color": (
291
+ "#9ca3af" if todo.done else "#1f2937"
292
+ ),
293
+ "fontSize": "16px"
294
+ }}
295
+ >
296
+ {todo.text}
297
+ </span>
298
+ <button
299
+ onClick={lambda -> None { deleteTodo(
300
+ todo.__jac_id
301
+ );}}
302
+ style={{
303
+ "padding": "6px 12px",
304
+ "background": "#ef4444",
305
+ "color": "#ffffff",
306
+ "border": "none",
307
+ "borderRadius": "6px",
308
+ "fontSize": "14px",
309
+ "fontWeight": "500",
310
+ "cursor": "pointer",
311
+ "transition": "background 0.2s"
312
+ }}
313
+ >
314
+ Delete
315
+ </button>
316
+ </div>; }
317
+ )
318
+ )}
319
+ </div>
320
+ # Stats and clear completed button
240
321
  {(
241
322
  <div
242
323
  style={{
243
- "padding": "40px",
244
- "textAlign": "center",
245
- "color": "#9ca3af"
324
+ "display": "flex",
325
+ "justifyContent": "space-between",
326
+ "alignItems": "center",
327
+ "marginTop": "24px",
328
+ "padding": "16px",
329
+ "background": "#ffffff",
330
+ "borderRadius": "12px",
331
+ "boxShadow": "0 1px 3px rgba(0,0,0,0.1)"
246
332
  }}
247
333
  >
334
+ <span style={{"color": "#6b7280", "fontSize": "14px"}}>
335
+ {activeCount}{"item" if activeCount == 1 else "items"}left
336
+ </span>
248
337
  {(
249
- "No todos yet. Add one above!"
250
- if filter == "all"
251
- else (
252
- "No active todos!"
253
- if filter == "active"
254
- else "No completed todos!"
255
- )
256
- )}
257
- </div>
258
- )
259
- if filteredTodos.length == 0
260
- else (
261
- filteredTodos.map(
262
- lambda todo: any -> any{ return <div
263
- key={todo._jac_id}
264
- style={{
265
- "display": "flex",
266
- "alignItems": "center",
267
- "gap": "12px",
268
- "padding": "16px",
269
- "borderBottom": "1px solid #e5e7eb",
270
- "transition": "background 0.2s"
271
- }}
272
- >
273
- <input
274
- type="checkbox"
275
- checked={todo.done}
276
- onChange={lambda -> None{ toggleTodo(todo._jac_id);} }
277
- style={{
278
- "width": "20px",
279
- "height": "20px",
280
- "cursor": "pointer"
281
- }}
282
- />
283
- <span
284
- style={{
285
- "flex": "1",
286
- "textDecoration": (
287
- "line-through" if todo.done else "none"
288
- ),
289
- "color": ("#9ca3af" if todo.done else "#1f2937"),
290
- "fontSize": "16px"
291
- }}
292
- >
293
- {todo.text}
294
- </span>
295
338
  <button
296
- onClick={lambda -> None{ deleteTodo(todo.__jac_id);} }
339
+ onClick={clearCompleted}
297
340
  style={{
298
- "padding": "6px 12px",
341
+ "padding": "8px 16px",
299
342
  "background": "#ef4444",
300
343
  "color": "#ffffff",
301
344
  "border": "none",
302
345
  "borderRadius": "6px",
303
346
  "fontSize": "14px",
304
- "fontWeight": "500",
305
- "cursor": "pointer",
306
- "transition": "background 0.2s"
347
+ "fontWeight": "600",
348
+ "cursor": "pointer"
307
349
  }}
308
350
  >
309
- Delete
351
+ Clear Completed
310
352
  </button>
311
- </div>; }
312
- )
313
- )}
314
- </div>
315
- # Stats and clear completed button
316
- {(
317
- <div
318
- style={{
319
- "display": "flex",
320
- "justifyContent": "space-between",
321
- "alignItems": "center",
322
- "marginTop": "24px",
323
- "padding": "16px",
324
- "background": "#ffffff",
325
- "borderRadius": "12px",
326
- "boxShadow": "0 1px 3px rgba(0,0,0,0.1)"
327
- }}
328
- >
329
- <span
330
- style={{"color": "#6b7280", "fontSize": "14px"}}
331
- >
332
- {activeCount} {"item" if activeCount == 1 else "items"} left
333
- </span>
334
- {(
335
- <button
336
- onClick={clearCompleted}
337
- style={{
338
- "padding": "8px 16px",
339
- "background": "#ef4444",
340
- "color": "#ffffff",
341
- "border": "none",
342
- "borderRadius": "6px",
343
- "fontSize": "14px",
344
- "fontWeight": "600",
345
- "cursor": "pointer"
346
- }}
347
- >
348
- Clear Completed
349
- </button>
350
- )
351
- if todos.some(lambda todo: any -> bool{ return todo.done; } )
352
- else None}
353
- </div>
354
- )
355
- if todos.length > 0
356
- else None}
357
- </div>;
353
+ )
354
+ if todos.some(lambda todo: any -> bool { return todo.done; })
355
+ else None}
356
+ </div>
357
+ )
358
+ if todos.length > 0
359
+ else None}
360
+ </div>;
358
361
  }
359
362
  }