mosaic-headless 1.29.2 → 1.30.0
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.
- package/README.md +17 -5
- package/SKILL.md +41 -2
- package/assets/templates/platforms/claude-ai.json +1 -1
- package/assets/templates/platforms/claude-code.json +1 -1
- package/assets/templates/platforms/codex-cli.json +1 -1
- package/assets/templates/platforms/copilot.json +1 -1
- package/assets/templates/platforms/gemini-cli.json +1 -1
- package/package.json +9 -2
- package/references/responsive.md +1 -0
- package/scripts/check-release.mjs +5 -1
- package/scripts/sync-version.mjs +3 -2
- package/sites/_components.py +560 -0
- package/sites/_content.py +341 -0
- package/sites/_conversion.py +225 -0
- package/sites/_dialogs.py +579 -0
- package/sites/_dynamic.py +311 -0
- package/sites/_forms.py +487 -0
- package/sites/_sliders.py +437 -0
- package/sites/_tokens.py +277 -0
- package/tools/build_page.py +4 -2
- package/tools/build_site.py +4 -2
- package/tools/copy_styles.py +4 -2
- package/tools/mosaic_config.py +71 -0
- package/tools/probe.py +4 -2
- package/tools/probe_accordion.py +4 -2
- package/tools/sweep_components.py +4 -2
- package/tools/sweep_interactions.py +4 -2
- package/tools/sweep_node_properties.py +4 -2
- package/tools/sweep_node_types.py +4 -2
- package/tools/sweep_properties.py +4 -2
- package/tools/sweep_style_properties.py +4 -2
- package/tools/sweep_style_states.py +4 -2
- package/tools/theme_zip.py +6 -3
- package/tools/verify_browser.py +4 -2
- package/tools/verify_intro.py +4 -2
- package/tools/verify_rwd.py +4 -2
- package/sites/moksa.json +0 -63681
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""The dynamic-content lab: a real loop over real posts, and the syntax nobody guesses.
|
|
2
|
+
|
|
3
|
+
Everything on this page that looks like content is an expression. The list of notes
|
|
4
|
+
is a `loop` node pointed at the `post` type; nothing in it is typed out, and adding
|
|
5
|
+
a post to the site adds a row here without anyone editing this page.
|
|
6
|
+
|
|
7
|
+
The syntax is the part worth being loud about, because the failure mode is silence.
|
|
8
|
+
A bare identifier - `@post.title`, which is what every other templating language in
|
|
9
|
+
the world would accept - is stored happily, renders as the literal text
|
|
10
|
+
`@post.title`, and reads like a typo in your copy rather than a broken feature.
|
|
11
|
+
`Evaluator::exec()` handles the `Identifier` node by returning the string
|
|
12
|
+
"Identifiers are not used currently". The real form is a function call:
|
|
13
|
+
`@VAR('post/title')`.
|
|
14
|
+
|
|
15
|
+
The loop's shape was read out of the source rather than guessed, and then probed:
|
|
16
|
+
|
|
17
|
+
- `LoopSourceTypeManager` keys its sources by `DynamicSourceReader::getIdentifier()`,
|
|
18
|
+
and `DynamicSourcePostTypes` builds that as `'wpPostType' . ucfirst($postType)`, so
|
|
19
|
+
`loopType` for ordinary posts is `wpPostTypePost`;
|
|
20
|
+
- `AbstractLoopSourceTypeFactory::getOptionsName()` is `getIdentifier() . 'Options'`,
|
|
21
|
+
so the options group is `wpPostTypePostOptions`;
|
|
22
|
+
- `loopNamespace` is the name the ROW variables answer to, so the content inside says
|
|
23
|
+
`@VAR('notes/title')` because the namespace here is `notes`.
|
|
24
|
+
|
|
25
|
+
`loop-items` and `loop-no-result` are omitted on purpose below: `LoopElementMResource`
|
|
26
|
+
heals them in, the same way a modal heals in its overlay. They are in
|
|
27
|
+
`data/default-children.csv` now, under `source` = `heal`.
|
|
28
|
+
"""
|
|
29
|
+
import csv
|
|
30
|
+
import io
|
|
31
|
+
import os
|
|
32
|
+
|
|
33
|
+
from _moksa import (CJK, DISPLAY, MONO, RULE, T, bp, box, grid, ml, mono,
|
|
34
|
+
section, wrap)
|
|
35
|
+
|
|
36
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
37
|
+
NS = "notes"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _table(name):
|
|
41
|
+
with io.open(os.path.join(_HERE, "..", "data", name), encoding="utf-8") as fh:
|
|
42
|
+
return list(csv.DictReader(fh))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
VARIABLES = _table("dynamic-variables.csv")
|
|
46
|
+
FUNCTIONS = [r["id"] for r in _table("evaluator-functions.csv")]
|
|
47
|
+
NAMESPACES = {}
|
|
48
|
+
for _r in VARIABLES:
|
|
49
|
+
NAMESPACES[_r["namespace"]] = NAMESPACES.get(_r["namespace"], 0) + 1
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def var(attr, expr, tag="p", _t=None, _m=None, **st):
|
|
53
|
+
"""A `wysiwyg-variable` inside a text node - the inline form of an expression.
|
|
54
|
+
|
|
55
|
+
It is one of the seven types that KILL the page under a plain container: it
|
|
56
|
+
needs a wysiwyg parent, and the sweep recorded the bare case as
|
|
57
|
+
`NodeMResourceFilterFunctionInterface parent is missing`.
|
|
58
|
+
"""
|
|
59
|
+
n = {"type": "text", "data": {"tagName": tag, "attrID": attr},
|
|
60
|
+
"children": [{"type": "wysiwyg-variable", "data": {"dynamicCode": expr}}]}
|
|
61
|
+
if st or _t or _m:
|
|
62
|
+
n["style"] = bp(st, _t, _m)
|
|
63
|
+
return n
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def code(attr, lines, bg="--mk-ink"):
|
|
67
|
+
return box(attr, {
|
|
68
|
+
"minWidth": "0px",
|
|
69
|
+
"paddingTop": "14px", "paddingBottom": "14px",
|
|
70
|
+
"paddingLeft": "16px", "paddingRight": "16px",
|
|
71
|
+
"backgroundColor": {"token": bg}, "overflow": "auto"},
|
|
72
|
+
[mono(l, size="10px", color="rgb(250,250,247)", track="0.02em",
|
|
73
|
+
whiteSpace="pre", marginTop="4px" if i else "0px")
|
|
74
|
+
for i, l in enumerate(lines)])
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
HEAD = section("dy-head", [wrap("dy-head-w", [
|
|
78
|
+
box("dy-head-pad", {"paddingTop": "116px"}, [], _m={"paddingTop": "26px"}),
|
|
79
|
+
mono("DYNAMIC CONTENT / 動態內容", size="11px",
|
|
80
|
+
color="--mk-faint", track="0.26em"),
|
|
81
|
+
ml("h1", "這一頁的清單,\n沒有人打過。",
|
|
82
|
+
fontFamily=DISPLAY, fontWeight="600", fontSize="58px", lineHeight="1.07",
|
|
83
|
+
letterSpacing="0.005em", color={"token": "--mk-ink"}, marginTop="18px",
|
|
84
|
+
_t={"fontSize": "44px"}, _m={"fontSize": "32px"}),
|
|
85
|
+
T("p",
|
|
86
|
+
"下面的筆記列表是一個 loop 節點,"
|
|
87
|
+
"指向站上的文章。標題、日期、摘要、連結"
|
|
88
|
+
"全部是表達式,不是文字——"
|
|
89
|
+
"站上多一篇文章,這裡就多一列,"
|
|
90
|
+
"沒有人需要回來改這頁。",
|
|
91
|
+
fontFamily=CJK, fontSize="16px", lineHeight="1.95",
|
|
92
|
+
color={"token": "--mk-ink"}, marginTop="22px", maxWidth="660px"),
|
|
93
|
+
box("dy-head-gap", {"paddingTop": "44px"}, []),
|
|
94
|
+
])], pad_y="0px")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
LOOP = section("dy-loop-s", [wrap("dy-loop-w", [
|
|
98
|
+
box("dy-loop-head", {
|
|
99
|
+
"display": "flex", "justifyContent": "space-between",
|
|
100
|
+
"alignItems": "baseline", "columnGap": "16px",
|
|
101
|
+
"paddingBottom": "14px",
|
|
102
|
+
"customDeclarations": "border-bottom:1px solid rgb(22,24,28);"},
|
|
103
|
+
[mono("實際跡的迴圈", size="10px", color="--mk-faint",
|
|
104
|
+
track="0.24em"),
|
|
105
|
+
mono("loopType = wpPostTypePost", size="10px", color="--mk-accent-ink",
|
|
106
|
+
track="0.1em")]),
|
|
107
|
+
{"type": "loop",
|
|
108
|
+
"data": {"attrID": "dy-loop", "loopType": "wpPostTypePost",
|
|
109
|
+
"loopNamespace": NS,
|
|
110
|
+
"wpPostTypePostOptions": {"maxItems": "6", "offset": "0"}},
|
|
111
|
+
"children": [
|
|
112
|
+
{"type": "loop-items", "data": {"attrID": "dy-items"},
|
|
113
|
+
"style": bp({"display": "grid", "gridCols": "repeat(2, 1fr)",
|
|
114
|
+
"columnGap": "20px", "rowGap": "0px"},
|
|
115
|
+
None, {"gridCols": "repeat(1, 1fr)"}),
|
|
116
|
+
"children": [
|
|
117
|
+
# ONE loop-item. Mosaic repeats it per row, and every copy carries
|
|
118
|
+
# the same id - the same template rule as a slider's bullet.
|
|
119
|
+
{"type": "loop-item", "data": {"attrID": "dy-item"},
|
|
120
|
+
"style": {"&": {"_": {
|
|
121
|
+
"display": "flex", "flexDirection": "column", "rowGap": "0px",
|
|
122
|
+
"paddingTop": "22px", "paddingBottom": "22px",
|
|
123
|
+
"customDeclarations": "border-bottom:1px solid " + RULE + ";"}}},
|
|
124
|
+
"children": [
|
|
125
|
+
box("dy-item-meta", {
|
|
126
|
+
"display": "flex", "columnGap": "12px",
|
|
127
|
+
"alignItems": "baseline"},
|
|
128
|
+
[var("dy-item-id", "@concat('#', @VAR('%s/id'))" % NS, "p",
|
|
129
|
+
fontFamily=MONO, fontSize="10px",
|
|
130
|
+
letterSpacing="0.16em",
|
|
131
|
+
color={"token": "--mk-accent-ink"},
|
|
132
|
+
_t={"fontSize": "12px"}),
|
|
133
|
+
# Two silent traps in one line. There is no `post/date`
|
|
134
|
+
# variable - the real ones are publish_date and
|
|
135
|
+
# publish_timestamp - and an unknown name renders as empty
|
|
136
|
+
# with no error. And @date takes the FORMAT first,
|
|
137
|
+
# PHP-style; reversed, it returns the raw timestamp rather
|
|
138
|
+
# than complaining. Both were found by looking at the page.
|
|
139
|
+
var("dy-item-date",
|
|
140
|
+
"@date('Y.m.d', @VAR('%s/publish_timestamp'))" % NS, "p",
|
|
141
|
+
fontFamily=MONO, fontSize="10px",
|
|
142
|
+
letterSpacing="0.12em",
|
|
143
|
+
color={"token": "--mk-faint"},
|
|
144
|
+
_t={"fontSize": "12px"})]),
|
|
145
|
+
var("dy-item-title", "@VAR('%s/title')" % NS, "h3",
|
|
146
|
+
fontFamily=DISPLAY, fontWeight="600", fontSize="20px",
|
|
147
|
+
lineHeight="1.3", letterSpacing="0.005em",
|
|
148
|
+
color={"token": "--mk-ink"}, marginTop="10px"),
|
|
149
|
+
var("dy-item-ex",
|
|
150
|
+
"@excerpt(@remove_html(@VAR('%s/content')), 60)" % NS, "p",
|
|
151
|
+
fontFamily=CJK, fontSize="13px", lineHeight="1.85",
|
|
152
|
+
color={"token": "--mk-muted"}, marginTop="8px"),
|
|
153
|
+
# a property that takes an expression uses the OBJECT form,
|
|
154
|
+
# because a literal URL is also legal in this slot
|
|
155
|
+
{"type": "button",
|
|
156
|
+
"data": {"attrID": "dy-item-go",
|
|
157
|
+
"url": {"v": "@VAR('%s/permalink')" % NS}},
|
|
158
|
+
"style": {"&": {"_": {
|
|
159
|
+
"backgroundColor": "rgba(0,0,0,0)",
|
|
160
|
+
"color": {"token": "--mk-ink"}, "fontFamily": MONO,
|
|
161
|
+
"fontSize": "10px", "letterSpacing": "0.18em",
|
|
162
|
+
"fontWeight": "500", "marginTop": "14px",
|
|
163
|
+
"width": "max-content",
|
|
164
|
+
"paddingTop": "8px", "paddingBottom": "8px",
|
|
165
|
+
"paddingLeft": "12px", "paddingRight": "12px",
|
|
166
|
+
"customDeclarations": "border:1px solid rgba(22,24,28,.28);"},
|
|
167
|
+
"_t": {"fontSize": "12px", "paddingTop": "10px",
|
|
168
|
+
"paddingBottom": "10px"}},
|
|
169
|
+
"hover": {"_": {"backgroundColor": {"token": "--mk-ink"},
|
|
170
|
+
"color": {"token": "--mk-paper"}}}},
|
|
171
|
+
"children": [{"type": "wysiwyg-text", "data": {"text": "READ"}}]},
|
|
172
|
+
]},
|
|
173
|
+
]},
|
|
174
|
+
{"type": "loop-no-result", "data": {"attrID": "dy-none"},
|
|
175
|
+
"children": [T("p", "這個查詢沒有結果。",
|
|
176
|
+
fontFamily=CJK, fontSize="14px",
|
|
177
|
+
color={"token": "--mk-muted"})]},
|
|
178
|
+
]},
|
|
179
|
+
box("dy-loop-foot", {"paddingTop": "56px"}, []),
|
|
180
|
+
])], pad_y="0px")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
SYNTAX = section("dy-syntax", [wrap("dy-syntax-w", [
|
|
184
|
+
box("dy-syntax-pad", {"paddingTop": "60px"}, []),
|
|
185
|
+
mono("SYNTAX", size="10px", color="--mk-faint", track="0.26em"),
|
|
186
|
+
ml("h2", "猜不到的是語法,不是功能。",
|
|
187
|
+
fontFamily=DISPLAY, fontWeight="600", fontSize="32px", lineHeight="1.15",
|
|
188
|
+
letterSpacing="0.005em", color={"token": "--mk-ink"}, marginTop="14px",
|
|
189
|
+
_m={"fontSize": "24px"}),
|
|
190
|
+
T("p",
|
|
191
|
+
"裸的識別字什麼都不做。"
|
|
192
|
+
"它會被乖乖儲存、原樣輸出,"
|
|
193
|
+
"看起來像你文案打錯字,而不像功能壞掉。"
|
|
194
|
+
"真正的寫法是一個函式呼叫。",
|
|
195
|
+
fontFamily=CJK, fontSize="15px", lineHeight="1.95",
|
|
196
|
+
color={"token": "--mk-ink"}, marginTop="14px", maxWidth="640px"),
|
|
197
|
+
box("dy-syntax-gap", {"paddingTop": "24px"}, []),
|
|
198
|
+
grid("dy-syntax-grid", 2, "20px", [
|
|
199
|
+
box("dy-wrong", {"minWidth": "0px"}, [
|
|
200
|
+
mono("沒有作用", size="10px", color="--mk-accent-ink",
|
|
201
|
+
track="0.22em"),
|
|
202
|
+
box("dy-wrong-gap", {"paddingTop": "10px"}, []),
|
|
203
|
+
code("dy-wrong-code",
|
|
204
|
+
["@post.title",
|
|
205
|
+
"",
|
|
206
|
+
"# 儲存成功、頁面正常、",
|
|
207
|
+
"# 輸出就是這串字本身。"]),
|
|
208
|
+
]),
|
|
209
|
+
box("dy-right", {"minWidth": "0px"}, [
|
|
210
|
+
mono("正確", size="10px", color="--mk-accent-ink", track="0.22em"),
|
|
211
|
+
box("dy-right-gap", {"paddingTop": "10px"}, []),
|
|
212
|
+
code("dy-right-code",
|
|
213
|
+
["@VAR('post/title')",
|
|
214
|
+
"@concat('#', @VAR('post/id'))",
|
|
215
|
+
"@fallback(@VAR('post/nope'), '—')",
|
|
216
|
+
"@excerpt(@remove_html(@VAR('post/content')), 60)"]),
|
|
217
|
+
]),
|
|
218
|
+
], tcols=1, mcols=1),
|
|
219
|
+
box("dy-syntax-foot", {"paddingTop": "26px"}, []),
|
|
220
|
+
T("p",
|
|
221
|
+
"一個表達式有兩種落脚處,"
|
|
222
|
+
"而哪一種是由屬性的驗證鏈決定的:"
|
|
223
|
+
"文字裡用 wysiwyg-variable 節點,字串直接寫;"
|
|
224
|
+
"屬性上(按鈕的 url、圖片的 src)則要包成 "
|
|
225
|
+
"{\"v\": \"…\"} 物件,因為那個位置也允許寫死的值。"
|
|
226
|
+
"ValidatorDynamicCode 是裸字串,ValidatorDynamicCodeObject 是物件。",
|
|
227
|
+
fontFamily=CJK, fontSize="14px", lineHeight="1.9",
|
|
228
|
+
color={"token": "--mk-muted"}, marginTop="18px", maxWidth="680px"),
|
|
229
|
+
box("dy-syntax-end", {"paddingTop": "62px"}, []),
|
|
230
|
+
])], bg="--mk-panel", pad_y="0px")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
SURFACE = section("dy-surface", [wrap("dy-surface-w", [
|
|
234
|
+
box("dy-surface-pad", {"paddingTop": "60px"}, []),
|
|
235
|
+
mono("THE SURFACE", size="10px", color="--mk-faint", track="0.26em"),
|
|
236
|
+
ml("h2", "%d 個變數、%d 個命名空間、%d 個函式。"
|
|
237
|
+
% (len(VARIABLES), len(NAMESPACES), len(FUNCTIONS)),
|
|
238
|
+
fontFamily=DISPLAY, fontWeight="600", fontSize="30px", lineHeight="1.18",
|
|
239
|
+
letterSpacing="0.005em", color={"token": "--mk-ink"}, marginTop="14px",
|
|
240
|
+
_m={"fontSize": "22px"}),
|
|
241
|
+
T("p",
|
|
242
|
+
"這三個數字跟下面的清單都是建置時"
|
|
243
|
+
"從萃取出來的表讀的,不是手打的。",
|
|
244
|
+
fontFamily=CJK, fontSize="14px", lineHeight="1.9",
|
|
245
|
+
color={"token": "--mk-muted"}, marginTop="10px", maxWidth="620px"),
|
|
246
|
+
box("dy-ns", {"marginTop": "22px", "display": "flex", "flexWrap": "wrap",
|
|
247
|
+
"columnGap": "8px", "rowGap": "8px"},
|
|
248
|
+
[box("dy-ns-%d" % i, {
|
|
249
|
+
"paddingTop": "7px", "paddingBottom": "7px",
|
|
250
|
+
"paddingLeft": "11px", "paddingRight": "11px",
|
|
251
|
+
"customDeclarations": "border:1px solid " + RULE + ";"},
|
|
252
|
+
[mono("%s / %d" % (n, c), size="10px", color="--mk-ink", track="0.06em")])
|
|
253
|
+
for i, (n, c) in enumerate(sorted(NAMESPACES.items(),
|
|
254
|
+
key=lambda kv: -kv[1]))]),
|
|
255
|
+
box("dy-fn", {"marginTop": "14px", "display": "flex", "flexWrap": "wrap",
|
|
256
|
+
"columnGap": "8px", "rowGap": "8px"},
|
|
257
|
+
[box("dy-fn-%d" % i, {
|
|
258
|
+
"paddingTop": "7px", "paddingBottom": "7px",
|
|
259
|
+
"paddingLeft": "11px", "paddingRight": "11px",
|
|
260
|
+
"backgroundColor": {"token": "--mk-panel"}},
|
|
261
|
+
[mono("@" + f, size="10px", color="--mk-ink", track="0.06em")])
|
|
262
|
+
for i, f in enumerate(FUNCTIONS)]),
|
|
263
|
+
box("dy-surface-foot", {"paddingTop": "60px"}, []),
|
|
264
|
+
])], pad_y="0px")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def trap(num, title, body):
|
|
268
|
+
return box("dy-trap-%d" % num, {}, [
|
|
269
|
+
mono("TRAP %02d" % num, size="10px", color="--mk-accent-ink", track="0.22em"),
|
|
270
|
+
ml("h3", title, fontFamily=DISPLAY, fontWeight="600", fontSize="17px",
|
|
271
|
+
lineHeight="1.3", color={"token": "--mk-ink"}, marginTop="10px"),
|
|
272
|
+
T("p", body, fontFamily=CJK, fontSize="13px", lineHeight="1.85",
|
|
273
|
+
color={"token": "--mk-muted"}, marginTop="8px"),
|
|
274
|
+
])
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
TRAPS = section("dy-traps", [wrap("dy-traps-w", [
|
|
278
|
+
box("dy-traps-inner", {
|
|
279
|
+
"paddingTop": "54px", "paddingBottom": "84px",
|
|
280
|
+
"customDeclarations": "border-top:1px solid " + RULE + ";"}, [
|
|
281
|
+
mono("踩過的坑", size="10px", color="--mk-faint", track="0.26em"),
|
|
282
|
+
box("dy-traps-gap", {"paddingTop": "20px"}, []),
|
|
283
|
+
grid("dy-traps-grid", 4, "22px", [
|
|
284
|
+
trap(1, "loop-item 是一個模板",
|
|
285
|
+
"你只寫一個,Mosaic 按資料筆數複製它,"
|
|
286
|
+
"所有複製品共用同一個 id——"
|
|
287
|
+
"跟輪播的小圓點是同一個規則。"
|
|
288
|
+
"寫三個 loop-item 就是三個模板,不是三列。"),
|
|
289
|
+
trap(2, "loop-items 不用自己寫",
|
|
290
|
+
"loop 會在 heal 階段補上 loop-items 和 "
|
|
291
|
+
"loop-no-result,跟 modal 補 overlay 是同一個機制。"
|
|
292
|
+
"這類「補進來的子節點」現在收在 "
|
|
293
|
+
"default-children.csv 的 source=heal 裡。"),
|
|
294
|
+
trap(3, "變數名寫錯,輸出是空的",
|
|
295
|
+
"沒有 post/date 這個變數(是 "
|
|
296
|
+
"publish_date 和 publish_timestamp),"
|
|
297
|
+
"而寫錯名字的結果是一個空字串——"
|
|
298
|
+
"不會報錯、不會警告。"
|
|
299
|
+
"這頁的日期欄第一版就是全部空白,"
|
|
300
|
+
"實際跡了才發現。"),
|
|
301
|
+
trap(4, "wysiwyg-variable 放錯地方會弄死頁面",
|
|
302
|
+
"它必須在 wysiwyg 父節點裡。"
|
|
303
|
+
"直接放在普通容器下會得到 "
|
|
304
|
+
"NodeMResourceFilterFunctionInterface parent is missing,"
|
|
305
|
+
"而且是整頁挂掉,不是這一個節點消失。"),
|
|
306
|
+
], tcols=1, mcols=1),
|
|
307
|
+
]),
|
|
308
|
+
])], pad_y="0px")
|
|
309
|
+
|
|
310
|
+
TREE = {"type": "div", "data": {"attrID": "dy-page"},
|
|
311
|
+
"children": [HEAD, LOOP, SYNTAX, SURFACE, TRAPS]}
|