volaro 0.1.0-alpha.6 → 0.1.0-alpha.8
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 +1 -1
- package/bin/vl.js +7 -1
- package/compiler/SOURCE_INFO.json +2 -2
- package/compiler/SOURCE_REV +1 -1
- package/compiler/validator/vlcheck/checks.py +28 -5
- package/compiler/validator/vlcheck/diagnostics.py +4 -1
- package/compiler/validator/vlcheck/resolve.py +87 -5
- package/compiler/vlbuild/vlbuild/__main__.py +7 -8
- package/compiler/vlbuild/vlbuild/assets/vlrouter.min.js +2 -0
- package/compiler/vlbuild/vlbuild/assets/vlrt.min.css +2 -0
- package/compiler/vlbuild/vlbuild/assets/vlrt.min.js +2 -0
- package/compiler/vlbuild/vlbuild/emit.py +21 -9
- package/compiler/vlbuild/vlbuild/pages_build.py +20 -10
- package/compiler/vlbuild/vlbuild/runtime_assets.py +41 -0
- package/compiler/vlbuild/vlbuild/static_assets.py +58 -11
- package/language/crib.md +7 -3
- package/language/spec.md +5 -2
- package/language/supported.md +8 -2
- package/package.json +1 -1
- package/scripts/selftest.mjs +19 -0
- package/scripts/sync-compiler.sh +12 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Volaro
|
|
2
2
|
|
|
3
|
-
**Limited alpha** (this is `0.1.0-alpha.
|
|
3
|
+
**Limited alpha** (this is `0.1.0-alpha.8`), published under both the
|
|
4
4
|
`latest` and `alpha` dist-tags: `npm install volaro`.
|
|
5
5
|
|
|
6
6
|
Volaro is an experimental application language intended for AI authoring and
|
package/bin/vl.js
CHANGED
|
@@ -69,6 +69,11 @@ function pyInvocation(pp, moduleName, moduleArgs) {
|
|
|
69
69
|
...process.env,
|
|
70
70
|
PYTHONPATH: [pp, VALIDATOR_PP, process.env.PYTHONPATH].filter(Boolean).join(delimiter),
|
|
71
71
|
PYTHONDONTWRITEBYTECODE: "1",
|
|
72
|
+
// Diagnostics and build summaries contain non-ASCII (box drawing, arrows).
|
|
73
|
+
// Without this, Windows encodes a redirected stdout with the legacy code
|
|
74
|
+
// page and the compiler dies with UnicodeEncodeError; the capturing path
|
|
75
|
+
// below also decodes the child's output as UTF-8.
|
|
76
|
+
PYTHONIOENCODING: "utf-8",
|
|
72
77
|
};
|
|
73
78
|
return { cmd: py.cmd, argv: ["-B", "-m", moduleName, ...moduleArgs], env };
|
|
74
79
|
}
|
|
@@ -744,9 +749,10 @@ COMMANDS
|
|
|
744
749
|
-o <dir> output directory
|
|
745
750
|
--data <file> data.json to bundle
|
|
746
751
|
--release fail on unresolved placeholders (e.g. img alt_todo:)
|
|
752
|
+
and ship the minified runtime
|
|
747
753
|
--benchmark-log <file> append raw timing records as JSONL
|
|
748
754
|
--benchmark-run <id> group attempts into one clean/recovery trial
|
|
749
|
-
dev [file.vl]
|
|
755
|
+
dev [file.vl] Build, serve on http://127.0.0.1:5173, and rebuild
|
|
750
756
|
when a .vl file changes. Defaults to ./app.vl. A
|
|
751
757
|
full-stack build runs server.js instead (Node 22.5+)
|
|
752
758
|
and restarts it after each successful rebuild.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"revision": "
|
|
2
|
+
"revision": "d2c835d1882b9ba794a8577752882c0e34dce872",
|
|
3
3
|
"source": "git",
|
|
4
4
|
"dirty": false,
|
|
5
|
-
"content_sha256": "
|
|
5
|
+
"content_sha256": "35f3e74ec054f9136bf783eed2f30674cb73db5bc9e4aed9a5d0e131186f7713"
|
|
6
6
|
}
|
package/compiler/SOURCE_REV
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
d2c835d1882b9ba794a8577752882c0e34dce872
|
|
@@ -2851,7 +2851,10 @@ class Checker:
|
|
|
2851
2851
|
if st is None:
|
|
2852
2852
|
return
|
|
2853
2853
|
if isinstance(st, A.Let):
|
|
2854
|
-
|
|
2854
|
+
if isinstance(st.init, A.Try):
|
|
2855
|
+
self._try(st.init, scopes, fallible, in_loop)
|
|
2856
|
+
else:
|
|
2857
|
+
self._expr(st.init, scopes, fallible)
|
|
2855
2858
|
self._declare(st.name, scopes, st)
|
|
2856
2859
|
if st.name and st.name != "?" and isinstance(scopes[-1], _Scope):
|
|
2857
2860
|
scopes[-1].kinds[st.name] = "mutable let" if st.mut else "`let` binding"
|
|
@@ -2889,10 +2892,7 @@ class Checker:
|
|
|
2889
2892
|
elif isinstance(st, A.Spawn):
|
|
2890
2893
|
self._block(st.body, scopes, fallible, in_loop=False)
|
|
2891
2894
|
elif isinstance(st, A.Try):
|
|
2892
|
-
self.
|
|
2893
|
-
for pat, body in st.catches:
|
|
2894
|
-
inner = scopes + [_Scope(getattr(pat, "binds", []), "catch binding")]
|
|
2895
|
-
self._scoped(body, inner, fallible, in_loop)
|
|
2895
|
+
self._try(st, scopes, fallible, in_loop)
|
|
2896
2896
|
elif isinstance(st, (A.Break, A.Skip)):
|
|
2897
2897
|
if not in_loop:
|
|
2898
2898
|
kw = "break" if isinstance(st, A.Break) else "skip"
|
|
@@ -2986,6 +2986,23 @@ class Checker:
|
|
|
2986
2986
|
def _block(self, stmts, scopes: list[set], fallible: bool, in_loop: bool = False) -> None:
|
|
2987
2987
|
self._scoped(stmts, scopes + [_Scope()], fallible, in_loop)
|
|
2988
2988
|
|
|
2989
|
+
def _try(self, t: A.Try, scopes: list[set], fallible: bool, in_loop: bool) -> None:
|
|
2990
|
+
"""`try EXPR` / `catch ...` in either position (spec §6.2): the
|
|
2991
|
+
statement form and `let NAME = try EXPR`."""
|
|
2992
|
+
if isinstance(t.expr, A.TryOp):
|
|
2993
|
+
# `try call()?` reads two ways and neither helps: `?` says
|
|
2994
|
+
# "propagate to the caller", `try`/`catch` says "handle it here".
|
|
2995
|
+
# The view build ignores the `?` (the catch arms run), so at best
|
|
2996
|
+
# it is redundant. Seen twice in Testing Lab login runs.
|
|
2997
|
+
self._d("W-TRY-PROPAGATE", Severity.WARNING,
|
|
2998
|
+
"'?' inside 'try' is redundant -- the catch arms below "
|
|
2999
|
+
"already handle the error, so nothing is propagated", t.expr,
|
|
3000
|
+
"remove the trailing '?'")
|
|
3001
|
+
self._expr(t.expr, scopes, fallible)
|
|
3002
|
+
for pat, body in t.catches:
|
|
3003
|
+
inner = scopes + [_Scope(getattr(pat, "binds", []), "catch binding")]
|
|
3004
|
+
self._scoped(body, inner, fallible, in_loop)
|
|
3005
|
+
|
|
2989
3006
|
def _expr(self, ex, scopes: list[set], fallible: bool) -> None:
|
|
2990
3007
|
if ex is None:
|
|
2991
3008
|
return
|
|
@@ -3022,6 +3039,12 @@ class Checker:
|
|
|
3022
3039
|
elif isinstance(ex, A.Range):
|
|
3023
3040
|
self._expr(ex.lo, scopes, fallible)
|
|
3024
3041
|
self._expr(ex.hi, scopes, fallible)
|
|
3042
|
+
elif isinstance(ex, A.Try):
|
|
3043
|
+
# expression-position `try` anywhere but a `let` initializer (which
|
|
3044
|
+
# `_stmt` routes to `_try` with the real loop context). `in_loop`
|
|
3045
|
+
# is assumed true here so a `break` / `skip` in such an arm is never
|
|
3046
|
+
# misreported; the emitter rejects the shapes it cannot lower.
|
|
3047
|
+
self._try(ex, scopes, fallible, in_loop=True)
|
|
3025
3048
|
elif isinstance(ex, A.TryOp):
|
|
3026
3049
|
if not fallible:
|
|
3027
3050
|
self._d("E-PROP-FALLIBLE", Severity.ERROR,
|
|
@@ -70,7 +70,10 @@ class Diagnostic:
|
|
|
70
70
|
f"{pad}│ {' ' * (col - 1)}{'^' * span}",
|
|
71
71
|
]
|
|
72
72
|
if self.fix:
|
|
73
|
-
|
|
73
|
+
# A multi-line fix (a code snippet) keeps its own indentation under
|
|
74
|
+
# the first line, instead of restarting at column 0.
|
|
75
|
+
head = f"{pad}= fix: "
|
|
76
|
+
out.append(head + self.fix.replace("\n", "\n" + " " * len(head)))
|
|
74
77
|
return "\n".join(out)
|
|
75
78
|
|
|
76
79
|
def to_json(self, li: LineIndex) -> dict:
|
|
@@ -17,7 +17,9 @@ type only where the type is written down or trivially known, and reports:
|
|
|
17
17
|
R-MISSING-ARG required parameter (no default, not optional-typed) not supplied
|
|
18
18
|
R-FIELD `x.f` where x's type is a known record / model and f is neither a field nor a method
|
|
19
19
|
R-MATCH `match` over a value of known union type, not exhaustive, no `_`
|
|
20
|
-
|
|
20
|
+
E-AUTH-METHOD `auth.f(...)` where `f` is not one of the eight §11.3 calls
|
|
21
|
+
(an E- code, not R-: `vlbuild` reports the same mistake
|
|
22
|
+
under the same code, so the check and build output match)
|
|
21
23
|
|
|
22
24
|
Deliberately conservative — an unknown callee, an unknown receiver type, an
|
|
23
25
|
element attribute value, or a `std.*` function (the spec gives no signature, and
|
|
@@ -39,7 +41,9 @@ exactly in `_route_name`.
|
|
|
39
41
|
|
|
40
42
|
from __future__ import annotations
|
|
41
43
|
|
|
44
|
+
import difflib
|
|
42
45
|
import os
|
|
46
|
+
import re
|
|
43
47
|
|
|
44
48
|
from . import ast_nodes as A
|
|
45
49
|
from .checks import BUILTINS, _PRIM_NAMES
|
|
@@ -93,6 +97,76 @@ _AUTH_API = {
|
|
|
93
97
|
"verify_totp": (("code", True),),
|
|
94
98
|
}
|
|
95
99
|
|
|
100
|
+
# The §11.3 calls the browser build target lowers today. The language accepts
|
|
101
|
+
# all eight above; `start_oauth` and `verify_totp` still fail `vlbuild`
|
|
102
|
+
# (E-AUTH-UNSUPPORTED) because OAuth and TOTP are not built (ROADMAP.md,
|
|
103
|
+
# features 04/05). Kept here, beside `_AUTH_API`, so `vlbuild`'s fail-closed
|
|
104
|
+
# check (`emit.py` `precheck`) and this checker's fix text read one list and
|
|
105
|
+
# cannot drift apart. Ordered for display: the password lifecycle.
|
|
106
|
+
AUTH_BUILT = ("sign_up", "sign_in", "sign_out", "reset_password",
|
|
107
|
+
"confirm_reset", "confirm_verify")
|
|
108
|
+
|
|
109
|
+
# Names agents carry over from other frameworks, mapped to the §11.3 call that
|
|
110
|
+
# does that job. Consulted before the spelling fallback so `auth.login`
|
|
111
|
+
# suggests `auth.sign_in`, which no edit-distance match would find.
|
|
112
|
+
_AUTH_ALIASES = {
|
|
113
|
+
"login": "sign_in", "log_in": "sign_in", "signin": "sign_in",
|
|
114
|
+
"authenticate": "sign_in", "sign_in_with_password": "sign_in",
|
|
115
|
+
"signup": "sign_up", "register": "sign_up", "create_account": "sign_up",
|
|
116
|
+
"create_user": "sign_up", "sign_up_with_password": "sign_up",
|
|
117
|
+
"logout": "sign_out", "log_out": "sign_out", "signout": "sign_out",
|
|
118
|
+
"forgot_password": "reset_password", "request_password_reset": "reset_password",
|
|
119
|
+
"send_password_reset": "reset_password", "reset": "reset_password",
|
|
120
|
+
"set_password": "confirm_reset", "update_password": "confirm_reset",
|
|
121
|
+
"verify_email": "confirm_verify", "confirm_email": "confirm_verify",
|
|
122
|
+
"verify": "confirm_verify",
|
|
123
|
+
"totp": "verify_totp", "verify_code": "verify_totp", "verify_otp": "verify_totp",
|
|
124
|
+
"oauth": "start_oauth", "sign_in_with_oauth": "start_oauth",
|
|
125
|
+
"login_with": "start_oauth",
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def auth_signature(method: str) -> str:
|
|
130
|
+
"""`auth.sign_in(email:, password:)` -- the §11.3 call as the spec's table
|
|
131
|
+
writes it (`start_oauth` takes its provider positionally)."""
|
|
132
|
+
params = _AUTH_API[method]
|
|
133
|
+
if method == "start_oauth":
|
|
134
|
+
return "auth.start_oauth(provider)"
|
|
135
|
+
return "auth.%s(%s)" % (method, ", ".join(n + ":" for n, _ in params))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def auth_method_suggestion(name: str) -> str | None:
|
|
139
|
+
"""The §11.3 call an unknown `auth.<name>` most likely meant, or None.
|
|
140
|
+
|
|
141
|
+
Shared with `vlbuild` so both tools suggest the same call. Tries, in
|
|
142
|
+
order: the name in snake_case (`signIn` -> `sign_in`), a known alias from
|
|
143
|
+
another framework (`login` -> `sign_in`), then a close spelling
|
|
144
|
+
(`confirm_verfiy` -> `confirm_verify`)."""
|
|
145
|
+
key = re.sub(r"(?<=[a-z0-9])(?=[A-Z])", "_", name).lower()
|
|
146
|
+
if key in _AUTH_API:
|
|
147
|
+
return key
|
|
148
|
+
if key in _AUTH_ALIASES:
|
|
149
|
+
return _AUTH_ALIASES[key]
|
|
150
|
+
close = difflib.get_close_matches(key, list(_AUTH_API), n=1, cutoff=0.75)
|
|
151
|
+
return close[0] if close else None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def auth_method_diagnostic(name: str) -> tuple[str, str]:
|
|
155
|
+
"""(message, fix) for an unknown `auth.<name>`. The checker prints them as
|
|
156
|
+
its message and `= fix:` line; `vlbuild`'s E-AUTH-METHOD joins the same two
|
|
157
|
+
strings, so both tools say the same thing."""
|
|
158
|
+
guess = auth_method_suggestion(name)
|
|
159
|
+
msg = "'auth.%s' is not part of the auth API" % name
|
|
160
|
+
msg += (" -- did you mean 'auth.%s'?" % guess) if guess else "."
|
|
161
|
+
msg += (" This build supports auth.%s and %s; verify_totp and "
|
|
162
|
+
"start_oauth are in the spec but not built"
|
|
163
|
+
% (", ".join(AUTH_BUILT[:-1]), AUTH_BUILT[-1]))
|
|
164
|
+
fix = auth_signature(guess) if guess else "use one of the calls listed above"
|
|
165
|
+
fix += ("; to call an existing HTTP endpoint such as /api/login, declare "
|
|
166
|
+
"an `api` block instead")
|
|
167
|
+
return msg, fix
|
|
168
|
+
|
|
169
|
+
|
|
96
170
|
# member names that are methods, not record fields — never flag `x.<this>`
|
|
97
171
|
_METHODS = {
|
|
98
172
|
"len", "format", "reload", "loading", "all", "first", "get", "where",
|
|
@@ -567,6 +641,16 @@ class Resolver:
|
|
|
567
641
|
self._expr(ex.hi, scopes)
|
|
568
642
|
elif isinstance(ex, A.TryOp):
|
|
569
643
|
self._expr(ex.operand, scopes)
|
|
644
|
+
elif isinstance(ex, A.Try):
|
|
645
|
+
# `let x = try CALL` / `x = try CALL`: the expression-position
|
|
646
|
+
# form (spec §6.2). Walked exactly like the statement form in
|
|
647
|
+
# `_stmt`. Without this branch nothing inside it was resolved, so
|
|
648
|
+
# `let r = try auth.login(...)` passed `volaro check` and only
|
|
649
|
+
# `vlbuild` refused it (Testing Lab 2026-09-27, volaro-F2).
|
|
650
|
+
self._expr(ex.expr, scopes)
|
|
651
|
+
for pat, body in ex.catches:
|
|
652
|
+
inner = scopes + [{b: None for b in getattr(pat, "binds", [])}]
|
|
653
|
+
self._scoped(body, inner)
|
|
570
654
|
elif isinstance(ex, A.IfExpr):
|
|
571
655
|
self._expr(ex.cond, scopes)
|
|
572
656
|
self._expr(ex.then, scopes)
|
|
@@ -709,10 +793,8 @@ class Resolver:
|
|
|
709
793
|
return
|
|
710
794
|
params = _AUTH_API.get(m)
|
|
711
795
|
if params is None:
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
"the §11.3 calls are: "
|
|
715
|
-
+ ", ".join(sorted(_AUTH_API)))
|
|
796
|
+
msg, fix = auth_method_diagnostic(m)
|
|
797
|
+
self._d("E-AUTH-METHOD", msg, ex, fix)
|
|
716
798
|
return
|
|
717
799
|
# Reuse the ordinary signature check: positional overflow, unknown
|
|
718
800
|
# named argument, and missing required argument all report the same way
|
|
@@ -31,8 +31,6 @@ from vlcheck import ast_nodes as A # noqa: E402
|
|
|
31
31
|
from .emit import BuildError, Emitter # noqa: E402
|
|
32
32
|
from .server_emit import ServerEmitter # noqa: E402
|
|
33
33
|
|
|
34
|
-
_ASSETS = Path(__file__).resolve().parent / "assets"
|
|
35
|
-
|
|
36
34
|
|
|
37
35
|
def _html(src_name: str, title: str | None = None, styled: bool = False) -> str:
|
|
38
36
|
# spec §8.11: the `page title:` becomes the document <title> when it is a
|
|
@@ -175,10 +173,9 @@ def build_file(src_path: Path, out_dir: Path, data_path: Path | None,
|
|
|
175
173
|
bundle.mkdir(parents=True, exist_ok=True)
|
|
176
174
|
copy_assets(asset_plan, bundle)
|
|
177
175
|
(bundle / "app.js").write_text(app_js, encoding="utf-8")
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
(bundle
|
|
181
|
-
encoding="utf-8")
|
|
176
|
+
# --release ships the committed minified runtime; see runtime_assets.py.
|
|
177
|
+
from .runtime_assets import write_runtime
|
|
178
|
+
write_runtime(bundle, ("vlrt.js", "vlrt.css"), release)
|
|
182
179
|
if app_css is not None:
|
|
183
180
|
(bundle / "app.css").write_text(app_css, encoding="utf-8")
|
|
184
181
|
(bundle / "index.html").write_text(
|
|
@@ -231,7 +228,8 @@ def main(argv: list[str]) -> int:
|
|
|
231
228
|
ap.add_argument("--data", type=Path, default=None,
|
|
232
229
|
help="data.json to bundle (default: look next to the source)")
|
|
233
230
|
ap.add_argument("--release", action="store_true",
|
|
234
|
-
help="fail on unresolved dev placeholders (e.g. img alt_todo:)"
|
|
231
|
+
help="fail on unresolved dev placeholders (e.g. img alt_todo:) "
|
|
232
|
+
"and ship the minified runtime")
|
|
235
233
|
args = ap.parse_args(argv)
|
|
236
234
|
|
|
237
235
|
src_path: Path = args.source
|
|
@@ -264,7 +262,8 @@ def main(argv: list[str]) -> int:
|
|
|
264
262
|
try:
|
|
265
263
|
config = load_project_config(src_path, args.config)
|
|
266
264
|
client_manifest = build_project(src_path, out_dir, config=config,
|
|
267
|
-
warnings=warnings
|
|
265
|
+
warnings=warnings,
|
|
266
|
+
release=args.release)
|
|
268
267
|
except BuildError as exc:
|
|
269
268
|
raise SystemExit("vlbuild: %s" % exc)
|
|
270
269
|
print("vlbuild: wrote %s/public/" % out_dir)
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/* minified vlrouter.js; readable source in the volaro package */
|
|
2
|
+
!function(t){"use strict";var n=t.VL;var e={text:function(t){return t.length>0},int:function(t){return/^-?[0-9]+$/.test(t)},uuid:function(t){return/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t)},slug:function(t){return/^[a-z0-9]+(-[a-z0-9]+)*$/.test(t)}};function r(t,n){return(e[t]||e.text)(n)?"int"===t?{ok:!0,value:parseInt(n,10)}:{ok:!0,value:n}:{ok:!1}}function o(t,n){var e=function(t){var n="/"===t?"/":t.replace(/\/+$/,"");if(""===n||"/"===n)return[];for(var e=n.replace(/^\/+/,"").split("/"),r=[],o=0;o<e.length;o++){var a;try{a=decodeURIComponent(e[o])}catch(t){return null}if(-1!==a.indexOf("/")||-1!==a.indexOf("\\"))return null;r.push(a)}return r}(n);if(null===e)return null;for(var r=0;r<t.pages.length;r++){var o=t.pages[r];if(o.segments.length===e.length){for(var a=!0,i=0;i<e.length;i++){var l=o.segments[i][0];if("static"===l&&o.segments[i][1]!==e[i]){a=!1;break}if("static"!==l&&"dynamic"!==l){a=!1;break}}if(a)return{page:o,rawSegments:e}}}return null}function a(t,n){for(var e={},r=0;r<n.length;r++)e[n[r]]=t[n[r]];return e}function i(t,n){var e=Object.keys(t),r=Object.keys(n);if(e.length!==r.length)return!1;for(var o=0;o<e.length;o++)if(t[e[o]]!==n[e[o]])return!1;return!0}var l=Date.now().toString(36)+Math.random().toString(36).slice(2,6)+".",u=0;function s(){return l+ ++u}function c(e,r){this.manifest=e,this.base=r||"",this.stack=[],this.generation=0,this.scrollMemory=Object.create(null),this.currentPage=null,this._currentKey=null,this._renderedUrl=null,this._nav=null,this._pending=null,this._announceTimer=null,this._announcer=function(t){if(!t||!t.body)return null;var n=t.getElementById("vl-announcer");return n||((n=t.createElement("div")).id="vl-announcer",n.setAttribute("role","status"),n.setAttribute("aria-live","polite"),n.setAttribute("aria-atomic","true"),n.style.cssText="position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;",t.body.appendChild(n),n)}(t.document);var o=this;n._onUnhandledLoadError=function(t){o._handleUnhandledLoadError(t)},n._onLoadSettled=function(){queueMicrotask(function(){o._checkPending()})}}function d(){var t=document.activeElement;return!t||t===document.body||!document.contains(t)}function h(){var t=document.getElementById("vl-main")||document.querySelector("main");t&&"function"==typeof t.focus&&t.focus({preventScroll:!0})}function f(t){var n=t.hash&&document.getElementById(function(t){try{return decodeURIComponent(t)}catch(n){return t}}(t.hash.slice(1)));n?n.scrollIntoView():window.scrollTo(0,0)}function p(t,n){return n<t.layouts.length?(t.boundary_for_layout_failure||[])[n]:t.boundaries.error}c.prototype._announce=function(t){var n=this._announcer;t=String(t||"").replace(/\s+/g," ").trim(),n&&t&&(clearTimeout(this._announceTimer),n.textContent="",this._announceTimer=setTimeout(function(){n.textContent=t},100))},c.prototype._announcePage=function(t){t&&(n._mainLabel=t,n.wireSkipLink(),this._announce(t))},c.prototype._stackTitle=function(){for(var t=this.stack.length-1;t>=0;t--)if(this.stack[t].entryTitle)return this.stack[t].entryTitle;return null},c.prototype._moduleUrl=function(t){return this.base+t+".js"},c.prototype._unmountFrom=function(t){for(var n=this.stack.length-1;n>=t;n--){var e=this.stack[n];e.host&&e.host._abortController&&e.host._abortController.abort(),e.el&&e.el.parentNode&&e.el.parentNode.removeChild(e.el)}this.stack.length=t},c.prototype._childrenSlotOf=function(t){return t?t.querySelector("[data-vl-children]"):null},c.prototype._keyCurrentEntry=function(){var t=history.state;if(t&&null!=t.vlNav)this._currentKey=t.vlNav;else{var n=s();try{history.replaceState(Object.assign({},t,{vlNav:n}),"")}catch(t){}this._currentKey=n}},c.prototype._saveScroll=function(){null!=this._currentKey&&(this.scrollMemory[this._currentKey]={x:window.scrollX,y:window.scrollY})},c.prototype._persistScroll=function(){this._saveScroll();var t=history.state;if(t&&t.vlNav===this._currentKey)try{history.replaceState(Object.assign({},t,{vlScroll:{x:window.scrollX,y:window.scrollY}}),"")}catch(t){}},c.prototype._savedScrollFor=function(t){if(null!=t&&this.scrollMemory[t])return this.scrollMemory[t];var n=history.state;return n&&n.vlNav===t&&n.vlScroll||null},c.prototype.navigate=function(t,n){n=n||{};var e=this,a=++this.generation,i=!!n.isPopstate,l=!!n.isInitial,u=new URL(t,location.href),c=u.pathname;l?this._keyCurrentEntry():i||this._persistScroll(),this._cancelPending();var d={gen:a,isInitial:l,loadingShown:!1};this._nav=d;var p,m=o(this.manifest,c);if(null===m)p=this._renderGlobalNotFound(a);else{var g=function(t,n){for(var e={},o=[],a=0;a<t.segments.length;a++){var i=t.segments[a][0],l=t.segments[a][1];if("dynamic"===i){var u=r(t.param_types&&t.param_types[l]||"text",n[a]);u.ok?e[l]=u.value:o.push(l)}}return{params:e,bad:o}}(m.page,m.rawSegments);p=g.bad.length?this._renderParamNotFound(m.page,g,a):this._renderChain(m.page,g.params,a)}return p.then(function(){if(a===e.generation){if(e._nav=null,!l&&!i){var t=s();history.pushState({vlNav:t},"",u.pathname+u.search+u.hash),e._currentKey=t}e._renderedUrl=u.pathname+u.search;var n=e._stackTitle();n&&(document.title=n);var r=i||l?e._savedScrollFor(e._currentKey):null;r?window.scrollTo(r.x,r.y):f(u),h(),l&&!d.loadingShown||e._announcePage(n)}})},c.prototype._renderGlobalNotFound=function(t){var n=this.manifest.global_not_found_layouts||[],e={layouts:n,layout_params:n.map(function(){return[]}),boundary_for_layout_failure:n.map(function(){return null}),boundaries:{error:null}};return this._renderNotFoundIn(e,n.length,{},this.manifest.global_not_found,t)},c.prototype._renderParamNotFound=function(t,n,e){for(var r=Math.min(t.not_found_layouts||0,t.layouts.length),o=0;o<r;){for(var a=t.layout_params[o]||[],i=!0,l=0;l<a.length;l++)if(-1!==n.bad.indexOf(a[l])){i=!1;break}if(!i)break;o++}return this._renderNotFoundIn(t,o,n.params,t.boundaries.not_found,e)},c.prototype._renderNotFoundIn=function(t,n,e,r,o){this.currentPage=t;var i=t.layouts.slice(0,n).map(function(n,r){return{moduleId:n,kind:"layout",params:a(e,t.layout_params[r]||[])}}).concat([{moduleId:r||null,kind:"boundary",params:{},fallback:m}]);return this._renderDesired(i,o,function(e){return e<n?{id:(t.boundary_for_layout_failure||[])[e],fallback:g}:{id:null,fallback:m}})},c.prototype._renderChain=function(t,n,e){this.currentPage=t;var r=t.layouts.map(function(e,r){return{moduleId:e,kind:"layout",params:a(n,t.layout_params[r]||[])}}).concat([{moduleId:t.module,kind:"page",params:n,loading:t.boundaries.loading}]);return this._renderDesired(r,e,function(n){return{id:p(t,n),fallback:g}})},c.prototype._renderDesired=function(t,n,e){for(var r=0;r<t.length&&r<this.stack.length;){var o=this.stack[r],a=t[r];if(o.moduleId!==a.moduleId||o.kind!==a.kind||!i(o.params,a.params)||o.el&&o.el.hidden)break;r++}this._unmountFrom(r);var l=0===r?document.getElementById("vl-root"):this._childrenSlotOf(this.stack[r-1].el);return this._mountFrom(t,r,l,n,e)},c.prototype._mountFrom=function(t,e,r,o,a){var i=this;if(e>=t.length)return Promise.resolve();var l=t[e];if("boundary"===l.kind&&!l.moduleId)return r&&this._mountFallback(r,l.fallback),Promise.resolve();var u="page"===l.kind&&l.loading&&r?this._beginLoadingWait(l.loading,r,o):null,s=function(){u&&u.cancel();var t=a(e);return i._mountBoundaryInto(t.id,r,o,t.fallback)};return n.loadModule(l.moduleId,this._moduleUrl(l.moduleId)).then(function(c){if(o!==i.generation||!r)return null;var d,h=document.createElement("div");u&&(h.hidden=!0),r.appendChild(h);try{d=n.mount(c.render,h,l.params)}catch(t){return r.removeChild(h),s()}var f={moduleId:l.moduleId,kind:l.kind,host:d,el:h,params:l.params,entryTitle:c.title||l.fallback&&l.fallback.title};return i.stack.push(f),u?u.waitFor(f):i._mountFrom(t,e+1,i._childrenSlotOf(h),o,a)},function(){return o!==i.generation?null:s()})},c.prototype._beginLoadingWait=function(t,n,e){var r,o=this,a={gen:e,frame:null,loadingEl:null,loadingHost:null,done:!1,replacing:!1},i=new Promise(function(t){r=t}),l=setTimeout(function(){o._showLoading(a,t,n)},300);function u(){clearTimeout(l),a.done=!0,o._pending===a&&(o._pending=null),a.loadingHost&&a.loadingHost._abortController&&a.loadingHost._abortController.abort(),a.loadingEl&&a.loadingEl.parentNode&&a.loadingEl.parentNode.removeChild(a.loadingEl)}return a.cancel=function(){a.done||(u(),r())},a.reveal=function(){a.done||(u(),a.frame&&a.frame.el&&(a.frame.el.hidden=!1),r())},a.waitFor=function(t){return a.frame=t,o._checkPending(),i},this._pending=a,a},c.prototype._cancelPending=function(){this._pending&&this._pending.cancel()},c.prototype._checkPending=function(){var t=this._pending;t&&!t.done&&t.frame&&!t.replacing&&(t.gen===this.generation?-1!==this.stack.indexOf(t.frame)&&function(t){if(!t||!t._loads)return!1;for(var n in t._loads)if(t._loads[n]&&t._loads[n].pending)return!0;return!1}(t.frame.host)||t.reveal():t.cancel())},c.prototype._showLoading=function(t,e,r){var o=this;t.done||t.gen!==this.generation||n.loadModule(e,this._moduleUrl(e)).then(function(e){if(!t.done&&t.gen===o.generation){var a=document.createElement("div");r.appendChild(a);var i=n._appEl;try{t.loadingHost=n.mount(e.render,a,{})}catch(t){return void r.removeChild(a)}finally{n._appEl=i}t.loadingEl=a;var l=o._nav;l&&l.gen===t.gen&&(l.loadingShown=!0,d()&&h(),l.isInitial||o._announce(a.innerText||a.textContent))}},function(){})};var m={title:"Not found",heading:"Not found"},g={title:"Error",text:"Something went wrong."};c.prototype._mountFallback=function(t,e){var r=t.closest&&t.closest("main"),o=document.createElement(r?"div":"main");if(r||(o.id="vl-main",o.tabIndex=-1),e.heading){var a=document.createElement("h1");a.textContent=e.heading,o.appendChild(a)}else o.textContent=e.text;t.appendChild(o),this.stack.push({moduleId:null,kind:"boundary",host:null,el:o,params:{},entryTitle:e.title}),n.wireSkipLink()},c.prototype._mountBoundaryInto=function(t,e,r,o){var a=this;return t?n.loadModule(t,this._moduleUrl(t)).then(function(i){if(r===a.generation&&e){var l,u=document.createElement("div");e.appendChild(u);try{l=n.mount(i.render,u,{})}catch(t){return e.removeChild(u),void a._mountFallback(e,o)}a.stack.push({moduleId:t,kind:"boundary",host:l,el:u,params:{},entryTitle:i.title||o.title})}},function(){r===a.generation&&e&&a._mountFallback(e,o)}):(e&&this._mountFallback(e,o),Promise.resolve())},c.prototype._handleUnhandledLoadError=function(t){for(var n=this,e=-1,r=0;r<this.stack.length;r++)if(this.stack[r].host===t){e=r;break}if(-1!==e&&this.currentPage){var o=this.generation,a=0===e?document.getElementById("vl-root"):this._childrenSlotOf(this.stack[e-1].el),i=this.currentPage,l=this._pending,u=!!(l&&!l.done&&l.frame&&this.stack.indexOf(l.frame)>=e);u&&(l.replacing=!0),this._unmountFrom(e),this._mountBoundaryInto(p(i,e),a,o,g).then(function(){if(o===n.generation){if(u)return l.replacing=!1,void l.reveal();var t=n._stackTitle();t&&(document.title=t),d()&&h(),n._announcePage(t)}})}},c.prototype.interceptLinks=function(n){var e=this;"scrollRestoration"in history&&(history.scrollRestoration="manual"),(n||document).addEventListener("click",function(t){if(!(t.defaultPrevented||0!==t.button||t.metaKey||t.ctrlKey||t.shiftKey||t.altKey)){var n=t.target.closest&&t.target.closest("a");if(n&&n.href){var r;try{r=new URL(n.href,location.href)}catch(t){return}r.origin===location.origin&&(n.target&&""!==n.target&&"_self"!==n.target||(r.hash&&r.pathname===location.pathname&&r.search===location.search?e._persistScroll():(t.preventDefault(),e.navigate(r.pathname+r.search+r.hash))))}}}),t.addEventListener("popstate",function(){if(e._saveScroll(),e._keyCurrentEntry(),location.pathname+location.search!==e._renderedUrl)e.navigate(location.pathname+location.search+location.hash,{isPopstate:!0});else{var t=e._savedScrollFor(e._currentKey);t?window.scrollTo(t.x,t.y):f(new URL(location.href))}}),t.addEventListener("hashchange",function(){e._keyCurrentEntry()}),t.addEventListener("pagehide",function(){e._persistScroll()})},n.Router=c,n.createRouter=function(t,n){return new c(t,n)}}("undefined"!=typeof window?window:globalThis);
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/* minified vlrt.css; readable source in the volaro package */
|
|
2
|
+
@layer vl-legacy{:root{--bg:#fbfbfa;--surface:#fff;--border:#e4e4e2;--ink:#1b1b1a;--muted:#6b6b68;--accent:#2f6f4f;--action-bg:#2f6f4f;--danger-bg:#b91c1c;--danger:#b91c1c;--warn:#b45309;color-scheme:light dark;accent-color:var(--accent)}@media (prefers-color-scheme:dark){:root{--bg:#16171a;--surface:#1e2024;--border:#33363c;--ink:#e9e9e7;--muted:#9a9a96;--accent:#6fcaa0;--danger:#e07070;--warn:#d99a3f}}*{box-sizing:border-box}body{background:var(--bg);color:var(--ink);margin:0;padding:24px;font:14px/1.5 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}code{color:var(--ink);background:var(--border);border-radius:3px;padding:1px 4px;font-size:12px}.vl-col{flex-direction:column;display:flex}.vl-row{flex-flow:wrap;display:flex}.vl-stack{display:grid}.vl-grid{grid-template-columns:repeat(auto-fit,minmax(150px,1fr));display:grid}.vl-text{display:inline-block}.vl-spacer{flex:1}.vl-fragment{display:contents}a.vl-link,a.vl-a{color:var(--accent);text-decoration:none}a.vl-link:hover,a.vl-a:hover{text-decoration:underline}input.vl-input,select.vl-select,textarea.vl-textarea{font:inherit;border:1px solid var(--border);background:var(--surface);color:var(--ink);border-radius:6px;padding:4px 8px}select.vl-select{min-height:32px}textarea.vl-textarea{resize:vertical;line-height:1.5}input.vl-input[aria-invalid=true],select.vl-select[aria-invalid=true],textarea.vl-textarea[aria-invalid=true]{border-color:var(--danger)}input.vl-input:is([type=checkbox],[type=radio],[type=range],[type=color]){background:0 0;border:0;padding:0}input.vl-input:is([type=checkbox],[type=radio]){width:16px;height:16px;margin:0}:is(input.vl-input,select.vl-select,textarea.vl-textarea,button.vl-button):disabled,fieldset.vl-fieldset:disabled{opacity:.55;cursor:not-allowed}.vl-field:has(>:disabled)>.vl-label{opacity:.55}.vl-field{flex-direction:column;gap:4px;display:flex}.vl-field>.vl-label{font-size:13px;font-weight:600}.vl-field>.vl-help{color:var(--muted);font-size:12px}.vl-field>.vl-error{white-space:normal;color:var(--danger);font-family:inherit;font-size:12px}.vl-field:has(>input.vl-input:is([type=checkbox],[type=radio])){grid-template-columns:auto 1fr;align-items:center;gap:2px 8px;display:grid}.vl-field:has(>input.vl-input:is([type=checkbox],[type=radio]))>input{grid-area:1/1}.vl-field:has(>input.vl-input:is([type=checkbox],[type=radio]))>.vl-label{font-size:inherit;grid-area:1/2;font-weight:400}.vl-field:has(>input.vl-input:is([type=checkbox],[type=radio]))>:is(.vl-help,.vl-error){grid-column:2}button.vl-button{font:inherit;border:1px solid var(--border);background:var(--surface);color:var(--ink);cursor:pointer;border-radius:6px;padding:6px 12px}button.vl-button[data-variant=primary]{background:var(--action-bg);color:#fff;border-color:#0000}button.vl-button[data-variant=ghost]{color:var(--accent);background:0 0;border-color:#0000}button.vl-button[data-variant=danger]{background:var(--danger-bg);color:#fff;border-color:#0000}button.vl-button[aria-pressed=true]{border-color:var(--accent);box-shadow:inset 0 0 0 2px var(--accent)}button.vl-button:not(:disabled):hover{border-color:var(--accent)}:is(input.vl-input,select.vl-select,textarea.vl-textarea,button.vl-button,a.vl-link,a.vl-a,summary.vl-summary):focus-visible{outline:3px solid var(--ink);outline-offset:3px}:is(.vl-col,.vl-row,.vl-stack,.vl-grid,.vl-card)>:is(.vl-p,.vl-h1,.vl-h2,.vl-h3,.vl-h4,.vl-h5,.vl-h6,.vl-ul,.vl-ol,.vl-menu,.vl-dl,.vl-table,.vl-figure,.vl-blockquote,.vl-pre,.vl-hr,.vl-fieldset,.vl-form,.vl-details){margin:0}fieldset.vl-fieldset{border:1px solid var(--border);border-radius:8px;flex-direction:column;gap:10px;min-width:0;padding:12px 16px 16px;display:flex}legend.vl-legend{padding:0 4px;font-size:13px;font-weight:600}table.vl-table{border-collapse:collapse;font-variant-numeric:tabular-nums}caption.vl-caption{text-align:start;padding-bottom:6px;font-weight:600}:is(th.vl-th,td.vl-td){text-align:start;border-bottom:1px solid var(--border);padding:6px 12px}thead.vl-thead th.vl-th{border-bottom-width:2px}details.vl-details{border:1px solid var(--border);background:var(--surface);border-radius:8px;padding:8px 12px}summary.vl-summary{cursor:pointer;font-weight:600}details.vl-details[open]>summary.vl-summary{margin-bottom:8px}progress.vl-progress,meter.vl-meter{width:100%;max-width:320px}.vl-overlay{z-index:1000;position:fixed;inset:0}.vl-overlay-dialog{background:#00000073;justify-content:center;align-items:center;padding:24px;display:flex}.vl-dialog{background:var(--surface);color:var(--ink);border:1px solid var(--border);border-radius:8px;width:100%;max-width:420px;padding:20px;box-shadow:0 8px 40px #00000040}.vl-dialog:focus{outline:none}.vl-dialog-title{margin:0 0 8px;font-size:18px;font-weight:700}.vl-tablist{border-bottom:1px solid var(--border);flex-direction:row;gap:4px;display:flex}.vl-tab{font:inherit;color:var(--muted);cursor:pointer;background:0 0;border:none;border-bottom:2px solid #0000;padding:6px 12px}.vl-tab[aria-selected=true]{color:var(--ink);border-bottom-color:var(--accent)}.vl-tab:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}.vl-tabpanel{padding:16px 0}.vl-tabpanel:focus{outline:none}.vl-spinner{color:var(--muted);padding:24px}.vl-error{color:var(--danger);white-space:pre-wrap;font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace}.vl-built-note{color:var(--muted);margin-top:32px;font-size:12px}.vl-skip{z-index:1000;background:var(--action-bg,#2f6f4f);color:#fff;border-radius:6px;padding:8px 14px;text-decoration:none;transition:top .15s ease-in-out;position:absolute;top:-60px;left:8px}.vl-skip:focus{outline:3px solid var(--ink,#111);outline-offset:2px;top:8px}:where(main):focus{outline:none}}@layer theme,utilities;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/* minified vlrt.js; readable source in the volaro package */
|
|
2
|
+
"use strict";!function(e){var t={},n=null;function r(e){return null==e?[]:e}t.fetchData=function(){return n||(n=e.fetch("data.json",{cache:"no-store"}).then(function(e){if(!e.ok)throw new Error("HTTP "+e.status+" loading data.json");return e.json()})),n},t.db={},t.qb=function(e){var t=(e||[]).slice(),n={where:function(e){var r={lt:function(e,t){return e<t},lte:function(e,t){return e<=t},gt:function(e,t){return e>t},gte:function(e,t){return e>=t},ne:function(e,t){return e!==t}};return Object.keys(e||{}).forEach(function(n){var i=e[n];if(null!=i&&""!==i)if(i&&"object"==typeof i&&"string"==typeof i.op)if("between"===i.op)t=t.filter(function(e){return e[n]>=i.v[0]&&e[n]<=i.v[1]});else if("any_of"===i.op){var o=i.v||[];t=t.filter(function(e){return-1!==o.indexOf(e[n])})}else{var a=r[i.op];t=t.filter(function(e){return a(e[n],i.v)})}else t=t.filter(function(e){return e[n]===i})}),n},order:function(e){var r=Object.keys(e||{})[0];if(r){var i="desc"===e[r]?-1:1;t.sort(function(e,t){return e[r]<t[r]?-i:e[r]>t[r]?i:0})}return n},limit:function(e){return t=t.slice(0,e),n},all:function(){return t},first:function(){return t.length?t[0]:null}};return n},t.std={filter:function(e,t){return r(e).filter(function(e){return t(e)})},map:function(e,t){return r(e).map(function(e){return t(e)})},take:function(e,t){return r(e).slice(0,t)},chunk:function(e,t){for(var n=r(e),i=[],o=0;o<n.length;o+=t)i.push(n.slice(o,o+t));return i},count:function(e){return r(e).length},sum:function(e){return r(e).reduce(function(e,t){return e+(t||0)},0)},sort_by:function(e,t){return r(e).slice().sort(function(e,n){var r=t(e),i=t(n);return r<i?-1:r>i?1:0})},group_by:function(e,t){var n={};return r(e).forEach(function(e){var r=t(e);(n[r]=n[r]||[]).push(e)}),n},join:function(e,t){return r(e).join(void 0===t?"":t)},split:function(e,t){return String(e).split(t)},replace:function(e,t,n){return String(e).split(String(t)).join(String(n))},first_char:function(e){return String(e).slice(0,1)},trim:function(e){return String(e).trim()},upper:function(e){return String(e).toUpperCase()},lower:function(e){return String(e).toLowerCase()},keys:function(e){return Object.keys(e||{})},values:function(e){return Object.values(e||{})},get_or:function(e,t,n){return e&&t in e?e[t]:n},int_to_float:function(e){return e},float_to_int:function(e){return Math.trunc(e)},concat:function(e,t){return r(e).concat(r(t))},append:function(e,t){return r(e).concat([t])},matches:function(e,t){try{return new RegExp(String(t)).test(String(e))}catch(e){return!1}}},t.len=function(e){return null==e?0:e.length},t.err=function(e,t){var n=new Error(t||e);return n.code=e,n},t.decode=function(e,n,r){return function e(n,i,o){if(null==n||"object"!=typeof n)throw t.err("decode.missing_field",(r||"response")+": expected an object at "+(o||"/"));for(var a in i)if(Object.prototype.hasOwnProperty.call(i,a)){if(void 0===n[a])throw t.err("decode.missing_field",(r||"response")+": missing "+o+"/"+a);i[a]&&"object"==typeof i[a]&&e(n[a],i[a],o+"/"+a)}return n}(e,n||{},"")},t.api=async function(n,r,i,o,a){var l,s,u,c={method:n,headers:{},credentials:"same-origin"};if(void 0!==i&&(c.headers["Content-Type"]="application/json",c.body=JSON.stringify(i)),/^(POST|PUT|PATCH|DELETE)$/.test(n)&&e.document){var d=(l=new RegExp("(?:^|;\\s*)"+(o||"vl_csrf").replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"=([^;]+)"),(s=(e.document&&e.document.cookie||"").match(l))?decodeURIComponent(s[1]):null);d&&(c.headers["X-CSRF-Token"]=d)}try{u=await e.fetch(r,c)}catch(e){if(!a)throw e;var f=t.err("transport.network",n+" "+r+" -> network failure");throw f.kind="transport",f}if(!u.ok){var v=null;try{v=await u.json()}catch(e){}if(a){if(!v||"object"!=typeof v.error||null===v.error||"string"!=typeof v.error.code){var h=t.err("transport.malformed",n+" "+r+" -> "+u.status);throw h.kind="transport",h}var p=v.error.code,y=t.err(p,n+" "+r+" -> "+u.status),m=v.error.fields;throw y.fields=m&&"object"==typeof m?m:{},y.kind=a.indexOf(p)>=0?"domain":u.status>=500?"internal":"framework",y}var g=v&&v.error?v.error:404===u.status?"not_found":"http."+u.status,b=t.err(g,n+" "+r+" -> "+u.status);throw v&&"object"==typeof v&&Object.keys(v).forEach(function(e){"error"!==e&&(b[e]=v[e])}),b}return 204===u.status?null:u.json()},t.authApi=async function(n,r,i){async function o(){var n=await e.fetch("/__vl/auth/csrf",{method:"GET",credentials:"same-origin",cache:"no-store"});if(!n.ok)throw t.err("http."+n.status,"could not establish CSRF token")}await o();try{return await t.api(n,r,i,"vl_csrf_pre")}catch(e){if(e&&"forbidden"===e.code)return await o(),t.api(n,r,i,"vl_csrf_pre");throw e}};var i=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function o(e,n){if(null!=e&&!0!==e&&!1!==e){if(Array.isArray(e))throw t.err("render.multi_root_row","a keyed list row must be a single element; key "+JSON.stringify(n)+" maps to "+e.length+" sibling nodes -- wrap the row in one `col` / `row`");e.nodeType&&(e._vlKey=n)}}function a(e,t){null!=t&&!1!==t&&!0!==t&&(Array.isArray(t)?t.forEach(function(t){a(e,t)}):"undefined"!=typeof Node&&t instanceof Node||t&&t.nodeType?e.appendChild(t):e.appendChild(document.createTextNode(String(t))))}function l(){return t._appEl||e.document&&e.document.getElementById("vl-root")||null}function s(e){for(var n=0;n<t._overlays.length;n++)if(t._overlays[n].id===e)return n;return-1}function u(e,t){if(e)try{t?(e.inert=!0,e.setAttribute("aria-hidden","true")):(e.inert=!1,e.removeAttribute("aria-hidden"))}catch(e){}}function c(e,t){if(!e||!t)return!1;if("boolean"==typeof e.isConnected)return e.isConnected;if("function"==typeof t.contains)return t.contains(e);for(var n=e;n.parentNode;)n=n.parentNode;return n===t||n===t.body||n===t.documentElement}function d(e){if(!e||!e.querySelectorAll)return[];for(var t=e.querySelectorAll("a[href],button,input,select,textarea,[tabindex]"),n=[],r=0;r<t.length;r++){var i=t[r];!i.disabled&&(null==i.tabIndex||i.tabIndex>=0||"-1"!==i.getAttribute("tabindex"))&&n.push(i)}return n}function f(e){var n=t.overlay._dismiss[e];"function"==typeof n&&n()}function v(e,t){if(!e||e.nodeType!==t.nodeType||e.nodeName!==t.nodeName)return t;if(3===t.nodeType)return e.nodeValue!==t.nodeValue&&(e.nodeValue=t.nodeValue),e;if(1!==t.nodeType||!e.attributes||!t.attributes)return t;if(e.getAttribute("data-vl-bind")!==t.getAttribute("data-vl-bind"))return t;if(!t._vlHandlers||!e._vlHandlers)return t;Object.keys(e._vlHandlers).forEach(function(t){e.removeEventListener(t,e._vlHandlers[t])}),Object.keys(t._vlHandlers).forEach(function(n){e.addEventListener(n,t._vlHandlers[n])}),e._vlHandlers=t._vlHandlers;var n="DETAILS"===e.nodeName?"open":null;return Array.from(e.attributes).forEach(function(r){r.name!==n&&(t.hasAttribute(r.name)||e.removeAttribute(r.name))}),Array.from(t.attributes).forEach(function(t){t.name!==n&&e.getAttribute(t.name)!==t.value&&e.setAttribute(t.name,t.value)}),h(e,Array.from(t.childNodes)),t.hasAttribute&&t.hasAttribute("value")&&e.value!==t.value&&(e.value=t.value),t.hasAttribute&&t.hasAttribute("data-vl-bind")&&e.checked!==t.checked&&(e.checked=t.checked),e}function h(e,t){for(var n=0;n<t.length;n++)if(t[n]&&null!=t[n]._vlKey)return p(e,t);for(t.forEach(function(t,n){var r=e.childNodes[n],i=v(r,t);r?i!==r&&e.replaceChild(i,r):e.appendChild(i)});e.childNodes.length>t.length;)e.removeChild(e.lastChild)}function p(n,r){var i=Array.prototype.slice.call(n.childNodes),o=Object.create(null),a=[];i.forEach(function(e){e&&null!=e._vlKey?e._vlKey in o||(o[e._vlKey]=e):a.push(e)});var l=Object.create(null);r.forEach(function(e){e&&null!=e._vlKey&&(l[e._vlKey]=!0)}),Object.keys(o).forEach(function(e){if(!l[e]){var n=o[e];n&&n._vlPfx&&t.pruneInstPrefix(n._vlHost,n._vlPfx)}});var s=e.document,u=s&&s.activeElement,c=null;u&&"number"==typeof u.selectionStart&&(c={start:u.selectionStart,end:u.selectionEnd,dir:u.selectionDirection});for(var f=[],h=0;h<t._overlays.length;h++){var p=t._overlays[h];if(p.restoreFocus)for(var y=0;y<i.length;y++){var m=i[y];if(m&&null!=m._vlKey&&!l[m._vlKey]&&m.contains&&m.contains(p.restoreFocus)){f.push({rec:p,index:y});break}}}var g=-1;if(u)for(var b=0;b<i.length;b++){var _=i[b];if(_&&null!=_._vlKey&&!l[_._vlKey]&&_.contains&&_.contains(u)){g=b;break}}var S=0,k=Object.create(null),w=[];r.forEach(function(e){if(e&&null!=e._vlKey){var n=e._vlKey;if(k[n])throw t.err("render.duplicate_key","list key "+JSON.stringify(n)+" is used by more than one row; keys must be unique within a list");k[n]=!0;var r=o[n],i=r?v(r,e):e;i._vlKey=n,w.push(i)}else{var l=a[S++];w.push(l?v(l,e):e)}});for(var E=0;E<w.length;E++)n.childNodes[E]!==w[E]&&n.insertBefore(w[E],n.childNodes[E]||null);for(;n.childNodes.length>w.length;)n.removeChild(n.lastChild);if(u&&s&&s.contains&&s.contains(u)&&s.activeElement!==u&&"function"==typeof u.focus){try{u.focus({preventScroll:!0})}catch(e){}if(c&&"function"==typeof u.setSelectionRange)try{u.setSelectionRange(c.start,c.end,c.dir)}catch(e){}}function A(e){for(var t=[],r=0;r<n.childNodes.length;r++)n.childNodes[r]&&null!=n.childNodes[r]._vlKey&&t.push(n.childNodes[r]);if(!t.length)return null;for(var o=0,a=0;a<e;a++){var s=i[a];s&&null!=s._vlKey&&l[s._vlKey]&&o++}var u=t[Math.min(o,t.length-1)];return d(u)[0]||u}if(g>=0&&s&&(!s.contains||!s.contains(u))){var C=A(g);if(C&&"function"==typeof C.focus)try{C.focus({preventScroll:!0})}catch(e){}}for(var x=0;x<f.length;x++)f[x].rec.restoreFallback=A(f[x].index)}t.fmt=function(e,t){var n=e instanceof Date?e:new Date(e);return isNaN(n.getTime())?String(e):String(t||"yyyy-MM-dd").replace(/yyyy/g,n.getFullYear()).replace(/MMM/g,i[n.getMonth()]).replace(/MM/g,("0"+(n.getMonth()+1)).slice(-2)).replace(/dd/g,("0"+n.getDate()).slice(-2)).replace(/\bd\b/g,n.getDate())},t.range=function(e,t,n){for(var r=[],i=e;n?i<=t:i<t;i++)r.push(i);return r},t.each=function(e,t){return r(e).map(function(e,n){return t(e,n)})},t.keyed=function(e,n,i){for(var a=r(e),l=[],s=Object.create(null),u=0;u<a.length;u++){var c=i(a[u],u);if(null==c)throw t.err("render.missing_key","list key expression produced "+String(c)+" for item "+u+"; a keyed list needs a stable, defined key per row");if(s[c=String(c)])throw t.err("render.duplicate_key","list key "+JSON.stringify(c)+" is used by more than one row; keys must be unique within a list");s[c]=!0;var d=n(a[u],u);o(d,c),l.push(d)}return l},t.escKey=function(e){return String(e).replace(/\\/g,"\\\\").replace(/\//g,"\\/")},t.tagInst=function(e,t,n){return n&&n.nodeType&&(n._vlPfx=e,n._vlHost=t),n},t.pruneInstPrefix=function(e,t){e&&t&&["_state","_mounted","_loads"].forEach(function(n){var r=e[n];r&&Object.keys(r).forEach(function(e){0===e.indexOf(t)&&delete r[e]})})},t.appendKids=a,t.testIdsEnabled=!1,t._testFrame=null,t._testScopes=[],t.withTestScope=function(e,n){if("string"!=typeof e||!/^[a-z][a-z0-9-]{0,79}$/.test(e))throw t.err("render.invalid_test_scope","test_scope must be a public lowercase slug");t._testScopes.push(e);try{return n()}finally{t._testScopes.pop()}},t.testId=function(e){var n=t._testScopes.concat([e]).join("/");if(t._testFrame){if(t._testFrame.ids.has(n))throw t.err("render.duplicate_test_id","duplicate automation ID in the next rendered document");t._testFrame.ids.add(n)}return n},t.linkSafeSchemes={http:1,https:1,mailto:1,tel:1},t.linkHref=function(e){if(null==e)return"";var n=String(e),r=n.replace(/[\u0000-\u0020]/g,""),i=/^([A-Za-z][A-Za-z0-9+\-.]*):/.exec(r);return i?t.linkSafeSchemes[i[1].toLowerCase()]?n:("undefined"!=typeof console&&console.warn&&console.warn("VL: blocked link scheme "+i[1].toLowerCase()+":"),"about:blank#blocked"):n},t.fetchSafeSchemes={http:1,https:1},t.fetchUrl=function(e){if(null==e)return"";var n=String(e),r=n.replace(/[\u0000-\u0020]/g,""),i=/^([A-Za-z][A-Za-z0-9+\-.]*):/.exec(r);return i?t.fetchSafeSchemes[i[1].toLowerCase()]?n:("undefined"!=typeof console&&console.warn&&console.warn("VL: blocked resource scheme "+i[1].toLowerCase()+":"),""):n},t.fetchSrcset=function(e){if(null==e)return"";for(var n=String(e),r=n.split(","),i=0;i<r.length;i++){var o=r[i].trim().split(/\s+/)[0];if(o&&""===t.fetchUrl(o))return""}return n},t.detailsToggle=function(e){return function(t){var n=t.currentTarget;if(n&&n.isConnected){if(!0===n._vlToggleSeeded)return e.call(this,t);n._vlToggleSeeded=!0}}},t.el=function(e,t,n){var r=document.createElement(e);return(t=t||{}).class&&(r.className=t.class),t.style&&Object.keys(t.style).forEach(function(e){r.style[e]=t.style[e]}),t.attrs&&Object.keys(t.attrs).forEach(function(e){var n=t.attrs[e];return"checked"===e?(r.checked=!!n,void(n?r.setAttribute("checked",""):r.removeAttribute("checked"))):"muted"===e?(r.muted=!!n,r.defaultMuted=!!n,void(n?r.setAttribute("muted",""):r.removeAttribute("muted"))):void(null!=n&&!1!==n&&r.setAttribute(e,!0===n?"":String(n)))}),void 0!==t.text&&null!==t.text&&(r.textContent=String(t.text)),"details"===e&&(r._vlToggleSeeded=!r.hasAttribute("open")),t.on&&Object.keys(t.on).forEach(function(e){r.addEventListener(e,t.on[e])}),r._vlHandlers=t.on||{},a(r,n),t.attrs&&"value"in t.attrs&&null!==t.attrs.value&&void 0!==t.attrs.value&&!1!==t.attrs.value&&(r.value=t.attrs.value),r},t.Spinner=function(){return t.el("div",{class:"vl-spinner",text:"loading…"},[])},t.live=function(e,n,r){if(null!=e&&"string"!=typeof e)throw t.err("render.invalid_status","status/error message must be a string or nil");r=Object.assign({},r||{},{role:"status","aria-live":"polite","aria-atomic":"true"});var i=t.el("span",{class:n||"vl-status",attrs:r},[]);return i.appendChild(document.createTextNode(e||"")),i},t._fieldSeq=0,t.field=function(e){var n=e.name;if(null==n||""===String(n).trim())throw t.err("render.empty_input_name","an input's accessible name ("+(e.aria?"aria_label:":"label:")+") is "+(null==n?String(n):JSON.stringify(String(n)))+" at render time; a control must always have a non-empty name");n=String(n);var r="vlf"+ ++t._fieldSeq,i=r+"c",o=r+"h",a=r+"e",l=e.input||{},s=l.attrs=l.attrs||{};s.id=i;var u=void 0!==e.help&&null!==e.help,c=e.hasError?e.error:void 0,d=null!=c&&""!==c,f=[];u&&f.push(o),d&&f.push(a),f.length?s["aria-describedby"]=f.join(" "):delete s["aria-describedby"],e.hasError&&(s["aria-invalid"]=d?"true":null),e.aria&&(s["aria-label"]=n);var v=[];return e.aria||v.push(t.el("label",{class:"vl-label",attrs:{for:i},text:n},[])),v.push(t.el(e.tag||"input",l,e.children||[])),u&&v.push(t.el("span",{class:"vl-help",attrs:{id:o},text:e.help},[])),e.hasError&&v.push(t.live(c,"vl-error",{id:a})),t.el("div",{class:"vl-field"},v)},t.reqAlt=function(e){if(null==e||""===String(e).trim())throw t.err("render.empty_alt","an img `alt:` resolved to "+(null==e?String(e):JSON.stringify(String(e)))+" at render time; use `decorative` for a presentational image or give real alternative text");return String(e)},t.setTitle=function(n){if(null==n||""===String(n).trim())throw t.err("render.empty_page_title","the page `title:` resolved to "+(null==n?String(n):JSON.stringify(String(n)))+" at load time; a page must have a non-empty title");var r=e.document;return r&&(r.title=String(n)),String(n)},t.wireSkipLink=function(){var n=e.document;if(n&&n.querySelector&&n.body){var r=n.querySelector("nav.vl-skip-nav"),i=n.querySelector("a.vl-skip"),o=n.querySelector("main");if(o){o.id||(o.id="vl-main"),o.getAttribute&&null==o.getAttribute("tabindex")&&o.setAttribute("tabindex","-1");var a=t._mainLabel;if(a&&o.getAttribute){var l=null!=o.getAttribute("data-vl-route-label");(null==o.getAttribute("aria-label")||l)&&(o.getAttribute("aria-label")!==a&&o.setAttribute("aria-label",a),l||o.setAttribute("data-vl-route-label",""))}if(i)i.getAttribute("href")!=="#"+o.id&&i.setAttribute("href","#"+o.id);else{var s=t.el("a",{class:"vl-skip",attrs:{href:"#"+o.id},text:"Skip to main content"},[]);s.addEventListener("click",function(t){t.preventDefault&&t.preventDefault();var r=n.querySelector("main")||n.getElementById("vl-main");r&&r.focus&&r.focus();try{r&&r.id&&e.history&&e.history.replaceState&&e.history.replaceState(null,"","#"+r.id)}catch(e){}}),r||(r=t.el("nav",{class:"vl-skip-nav",attrs:{"aria-label":"Skip navigation"}},[]),n.body.insertBefore(r,n.body.firstChild)),r.appendChild(s)}}else{var u=r||i;u&&u.parentNode&&u.parentNode.removeChild(u)}}},t.queryProps=function(t){var n={};try{var r=e.location,i=new URLSearchParams(r&&r.search||"");(t||[]).forEach(function(e){i.has(e)&&(n[e]=i.get(e))})}catch(e){}return n},t._overlays=[],t._appEl=null,t.overlay={open:function(n){var r=s(n);if(r>=0)return t._overlays[r].el;var i=e.document,o=i.createElement("div");o.className="vl-overlay",o.setAttribute("data-overlay",String(n));var a=i.body||l()&&l().parentNode||null;a&&a.appendChild(o);var c=i.activeElement;return t._overlays.push({id:n,el:o,restoreFocus:c&&c!==i.body?c:null}),1===t._overlays.length&&u(l(),!0),o},close:function(n){var r=s(n);if(!(r<0)){var i=t._overlays.splice(r,1)[0],o=e.document;i._onClick&&i.el.removeEventListener&&i.el.removeEventListener("click",i._onClick),i._onTab&&i.el.removeEventListener&&i.el.removeEventListener("keydown",i._onTab),i._onKey&&o&&o.removeEventListener&&o.removeEventListener("keydown",i._onKey),delete t.overlay._dismiss[n],i.el&&i.el.parentNode&&i.el.parentNode.removeChild(i.el),0===t._overlays.length&&(u(l(),!1),function(e,t){for(var n=[e.restoreFocus,e.restoreFallback,t&&t.getElementById&&t.getElementById("vl-main")],r=0;r<n.length;r++){var i=n[r];if(i&&c(i,t)&&"function"==typeof i.focus){try{i.focus()}catch(e){}if(!t||t.activeElement===i)return}}var o=e.restoreFocus;if(o&&"function"==typeof o.focus)try{o.focus()}catch(e){}}(i,o))}},focusInto:function(e){if(e){var t=d(e)[0]||e;if(t===e&&(null==e.tabIndex||e.tabIndex<0)&&(e.tabIndex=-1),"function"==typeof t.focus)try{t.focus()}catch(e){}}},count:function(){return t._overlays.length},_dismiss:Object.create(null),_live:null,beginFrame:function(){this._live=Object.create(null)},endFrame:function(){var e=this._live;if(this._live=null,e)for(var n=t._overlays.length-1;n>=0;n--){var r=t._overlays[n].id;r in e||this.close(r)}},paint:function(n,r,i,o){var a;if(t._testFrame)return t._testFrame.portals.push(function(){t.overlay.paint(n,r,function(){return a},o)}),a=i(),null;var l=s(n)<0,u=this.open(n);u.className="vl-overlay vl-overlay-dialog",this._live&&(this._live[n]=!0),this._dismiss[n]=o;var c=t._overlays[s(n)],h="vl-dlg-title-"+String(n).replace(/[^\w-]/g,"_"),p=t.el("div",{class:"vl-dialog",attrs:{role:"dialog","aria-modal":"true","aria-labelledby":h,tabindex:"-1"}},[t.el("h2",{class:"vl-dialog-title",attrs:{id:h},text:r},[]),i()]);if(c.panel&&c.panel.parentNode===u&&u.replaceChild){var y=v(c.panel,p);y!==c.panel&&(u.replaceChild(y,c.panel),c.panel=y)}else if(u.replaceChildren)u.replaceChildren(p),c.panel=p;else{for(;u.firstChild;)u.removeChild(u.firstChild);u.appendChild(p),c.panel=p}return l&&(c._onTab=function(t){if("Tab"===(t.key||"")||9===t.keyCode){var n=c.panel,r=d(n);if(r.length){var i=r[0],o=r[r.length-1],a=e.document.activeElement;t.shiftKey&&a===i?(t.preventDefault&&t.preventDefault(),o.focus()):t.shiftKey||a!==o&&n.contains(a)||(t.preventDefault&&t.preventDefault(),i.focus())}else t.preventDefault&&t.preventDefault()}},u.addEventListener&&u.addEventListener("keydown",c._onTab),c._onClick=function(e){e.target===u&&f(n)},u.addEventListener&&u.addEventListener("click",c._onClick),c._onKey=function(e){"Escape"!==(e.key||"")&&27!==e.keyCode||f(n)},e.document.addEventListener&&e.document.addEventListener("keydown",c._onKey),this.focusInto(c.panel)),null}},t.route={to:function(t){if("string"==typeof t&&t)try{if("?"===t[0]||"#"===t[0]){var n=e.history;return void(n&&n.replaceState&&n.replaceState(null,"",t))}var r=e.location;r&&r.assign&&r.assign(t)}catch(e){}}},t.tabs=function(e){for(var n=(e=e||{}).tabs||[],r=0,i=0;i<n.length;i++)if(n[i].id===e.selected){r=i;break}var o=n.length?n[r].id:null;function a(t){n.length&&e.onSelect&&e.onSelect(n[(t+n.length)%n.length].id)}var l=t.el("div",{class:"vl-tablist",attrs:{role:"tablist"},on:{keydown:function(e){var t=e.key||"",i=function(){e.preventDefault&&e.preventDefault()};"ArrowRight"===t||"ArrowDown"===t?(i(),a(r+1)):"ArrowLeft"===t||"ArrowUp"===t?(i(),a(r-1)):"Home"===t?(i(),a(0)):"End"===t&&(i(),a(n.length-1))}}},n.map(function(n){var r=n.id===o;return t.el("button",{class:"vl-tab",attrs:{role:"tab",id:"vl-tab-"+n.id,"aria-selected":r?"true":"false","aria-controls":"vl-panel-"+n.id,tabindex:r?"0":"-1"},text:n.label,on:{click:function(){e.onSelect&&e.onSelect(n.id)}}},[])})),s=n[r],u=t.el("div",{class:"vl-tabpanel",attrs:{role:"tabpanel",id:"vl-panel-"+o,"aria-labelledby":"vl-tab-"+o,tabindex:"0"}},s?[s.panel()]:[]);return t.el("div",{class:"vl-tabs"},[l,u])},t.subctx=function(e,t){var n=e._host,r=(e._pfx||"")+(t||"");return{_pfx:r,_host:n,signal:n.signal,state:function(e,t){return n.state(r+e,t)},setState:function(e,t){return n.setState(r+e,t)},load:function(e,t,i,o){return n.load(r+e,t,i,o)},reload:function(e){return n.reload(r+e)},mountOnce:function(e,t){return n.mountOnce(r+e,t)}}},t.mount=function(n,r,i){var o=i||{};t._appEl=r;var l="undefined"!=typeof AbortController?new AbortController:null,s={_state:Object.create(null),_loads:Object.create(null),_mounted:Object.create(null),_scheduled:!1,_composing:!1,_abortController:l,signal:l?l.signal:void 0,state:function(e,t){return e in this._state||(this._state[e]=t),this._state[e]},mountOnce:function(e,t){this._mounted[e]||(this._mounted[e]=!0,t())},setState:function(e,t){return this._state[e]=t,this.schedule(),t},load:function(e,n,r,i){var o=JSON.stringify(void 0===n?null:n),a=this._loads[e];if(!a||a.key!==o){a=this._loads[e]={key:o,pending:!0,error:null,value:void 0};var l=this;Promise.resolve().then(r).then(function(n){l._loads[e]===a&&(a.pending=!1,a.value=n,l.schedule(),t._onLoadSettled&&t._onLoadSettled(l))},function(n){l._loads[e]===a&&(a.pending=!1,a.error=n,l.schedule(),!i&&t._onUnhandledLoadError&&t._onUnhandledLoadError(l,e,n),t._onLoadSettled&&t._onLoadSettled(l))})}return a},reload:function(e){delete this._loads[e],this.schedule()},schedule:function(){var e=this;e._scheduled||(e._scheduled=!0,queueMicrotask(function(){e._scheduled=!1,e.render()}))},render:function(){if(!this._composing){var i,l=e.document,s=l&&l.activeElement,c=null;s&&s.getAttribute&&s.getAttribute("data-vl-bind")&&(!l.contains||l.contains(s))&&(c={node:s,key:s.getAttribute("data-vl-bind"),start:s.selectionStart,end:s.selectionEnd,direction:s.selectionDirection,scrollLeft:s.scrollLeft,scrollTop:s.scrollTop}),t.overlay.beginFrame(),t.testIdsEnabled&&(t._testFrame={ids:new Set,portals:[]});try{i=n(o,u);var d=t._testFrame;t._testFrame=null,d&&d.portals.forEach(function(e){e()})}catch(e){if(t._testFrame=null,t._testScopes=[],e&&/^render\.(duplicate_test_id|invalid_test_scope)$/.test(e.code||""))throw t.overlay._live=null,e;throw r.replaceChildren(t.el("pre",{class:"vl-error",text:String(e&&e.stack||e)},[])),e}if(Array.isArray(i)){var f=document.createDocumentFragment();a(f,i),r.replaceChild?h(r,Array.from(f.childNodes)):r.replaceChildren(f)}else r.replaceChild?h(r,[i]):r.replaceChildren(i);if(t.overlay.endFrame(),t.wireSkipLink(),c&&l){var v=l.contains&&l.contains(c.node)?c.node:null;if(!v&&l.querySelectorAll){var p=Array.prototype.filter.call(l.querySelectorAll("[data-vl-bind]"),function(e){return e.getAttribute("data-vl-bind")===c.key});1===p.length&&(v=p[0])}if(v&&l.activeElement!==v){if(v.focus({preventScroll:!0}),null!=c.start&&"function"==typeof v.setSelectionRange)try{v.setSelectionRange(c.start,c.end,c.direction)}catch(e){}v.scrollLeft=c.scrollLeft,v.scrollTop=c.scrollTop}}}}},u=t.subctx({_pfx:"",_host:s},""),c=e.document;return c&&c.addEventListener&&(c.addEventListener("compositionstart",function(){s._composing=!0}),c.addEventListener("compositionend",function(){s._composing=!1,s.schedule()})),s.render(),s},t._onUnhandledLoadError=null,t._onLoadSettled=null,t._mainLabel=null,t._modules=Object.create(null);var y=Object.create(null);t.registerModule=function(e,n){t._modules[e]=n},t.loadModule=function(n,r){if(t._modules[n])return Promise.resolve(t._modules[n]);if(y[n])return y[n];var i=e.document,o=new Promise(function(e,o){var a=i.createElement("script");a.src=r,a.onload=function(){t._modules[n]?e(t._modules[n]):o(new Error("module '"+n+"' did not register itself (loaded "+r+")"))},a.onerror=function(){o(new Error("failed to load module '"+n+"' from "+r))},i.head.appendChild(a)});return y[n]=o,o},e.VL=t}("undefined"!=typeof window?window:globalThis);
|
|
@@ -62,7 +62,8 @@ from vlcheck.elements import (
|
|
|
62
62
|
from vlcheck.checks import _lit_int, _lit_text, _rendered_children
|
|
63
63
|
from vlcheck.lexer import Lexer
|
|
64
64
|
from vlcheck.parser import Parser
|
|
65
|
-
from vlcheck.resolve import _route_name
|
|
65
|
+
from vlcheck.resolve import (_AUTH_API, AUTH_BUILT, _route_name,
|
|
66
|
+
auth_method_diagnostic)
|
|
66
67
|
|
|
67
68
|
|
|
68
69
|
def table_name(model_name: str) -> str:
|
|
@@ -637,15 +638,26 @@ class Emitter:
|
|
|
637
638
|
faithfully, rather than write a bundle that silently does less than the
|
|
638
639
|
source says. The counterpart to `ServerEmitter.precheck` for the browser
|
|
639
640
|
target. A narrow subset is fine; failing open is not."""
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
641
|
+
# Two different mistakes, two codes. A name outside spec §11.3 is
|
|
642
|
+
# E-AUTH-METHOD, the code `volaro check` reports for it too (resolve.py),
|
|
643
|
+
# so this is the backstop for source that never went through the
|
|
644
|
+
# checker. A §11.3 call this target does not build (OAuth, TOTP) is
|
|
645
|
+
# E-AUTH-UNSUPPORTED. Both fail closed: emitting either call would
|
|
646
|
+
# produce a client with no server endpoint behind it.
|
|
647
|
+
unknown_auth = sorted(self.auth_methods - set(_AUTH_API))
|
|
648
|
+
if unknown_auth:
|
|
649
|
+
msg, fix = auth_method_diagnostic(unknown_auth[0])
|
|
650
|
+
raise BuildError("E-AUTH-METHOD: view target: %s. fix: %s"
|
|
651
|
+
% (msg, fix))
|
|
652
|
+
unbuilt_auth = sorted(self.auth_methods - set(AUTH_BUILT))
|
|
653
|
+
if unbuilt_auth:
|
|
644
654
|
raise BuildError(
|
|
645
|
-
"view target: auth.%s is
|
|
646
|
-
"
|
|
647
|
-
"
|
|
648
|
-
|
|
655
|
+
"E-AUTH-UNSUPPORTED: view target: auth.%s is in the spec but "
|
|
656
|
+
"this build does not implement it (OAuth and TOTP are not "
|
|
657
|
+
"built), so emitting the call would fail open. The auth calls "
|
|
658
|
+
"this build supports are %s"
|
|
659
|
+
% (" / auth.".join(unbuilt_auth),
|
|
660
|
+
", ".join("auth." + x for x in AUTH_BUILT)))
|
|
649
661
|
if self.auth_methods and self.auth is None:
|
|
650
662
|
raise BuildError(
|
|
651
663
|
"view target: auth.%s is called but no project `auth` block was "
|
|
@@ -36,6 +36,7 @@ from vlcheck.pagemanifest import (build_manifest, resolve_boundary,
|
|
|
36
36
|
from vlcheck.resolve import resolve
|
|
37
37
|
|
|
38
38
|
from .emit import BuildError, Emitter
|
|
39
|
+
from .static_assets import copy_assets, prepare_assets
|
|
39
40
|
|
|
40
41
|
_SAFE_RE = re.compile(r"[^A-Za-z0-9_]+")
|
|
41
42
|
|
|
@@ -209,6 +210,7 @@ def build_pages(root: Path, out_public: Path,
|
|
|
209
210
|
collected: list[str] = [] if warnings is None else warnings
|
|
210
211
|
|
|
211
212
|
compiled: dict[str, str] = {} # module id -> emitted JS
|
|
213
|
+
asset_plan = None # the same for every file; set per compiled file
|
|
212
214
|
for path, info in files.items():
|
|
213
215
|
mod, parse_diags, src = _parse(path)
|
|
214
216
|
if titles is not None and info["role"] == "page":
|
|
@@ -259,6 +261,15 @@ def build_pages(root: Path, out_public: Path,
|
|
|
259
261
|
except BuildError as exc:
|
|
260
262
|
problems.append(f"{exc} ({path})")
|
|
261
263
|
continue
|
|
264
|
+
# A project's entry is its root, so its sibling assets/ is
|
|
265
|
+
# root/assets/, shipped at public/assets/ (spec: the build copies the
|
|
266
|
+
# entry's assets/ into the browser bundle). Each file's literal image
|
|
267
|
+
# references are checked here, before anything is written.
|
|
268
|
+
try:
|
|
269
|
+
asset_plan = prepare_assets(root / "app.vl", out_public, emitter)
|
|
270
|
+
except BuildError as exc:
|
|
271
|
+
problems.append(f"{exc} ({path})")
|
|
272
|
+
continue
|
|
262
273
|
compiled[mid] = js
|
|
263
274
|
style_candidates.extend(emitter.style_candidates)
|
|
264
275
|
collected.extend("%s (%s)" % (w, os.path.relpath(path, root)) for w in emitter.warnings)
|
|
@@ -286,6 +297,8 @@ def build_pages(root: Path, out_public: Path,
|
|
|
286
297
|
pages_dir.mkdir(parents=True, exist_ok=True)
|
|
287
298
|
for mid, js in compiled.items():
|
|
288
299
|
(pages_dir / f"{mid}.js").write_text(js, encoding="utf-8")
|
|
300
|
+
if asset_plan is not None:
|
|
301
|
+
copy_assets(asset_plan, out_public)
|
|
289
302
|
|
|
290
303
|
def boundary_id(path: str | None) -> str | None:
|
|
291
304
|
return module_id_for(str(root), path) if path else None
|
|
@@ -354,8 +367,6 @@ def static_routes(client_manifest: dict) -> list[dict]:
|
|
|
354
367
|
if not any(kind == "dynamic" for kind, _ in p["segments"])]
|
|
355
368
|
|
|
356
369
|
|
|
357
|
-
_ASSETS = Path(__file__).resolve().parent / "assets"
|
|
358
|
-
|
|
359
370
|
# The route-agnostic shell the server fallback answers with (a dynamic route,
|
|
360
371
|
# or any path the manifest does not know). A static route's own shell is the
|
|
361
372
|
# same file with that page's title (`build_project`).
|
|
@@ -423,7 +434,8 @@ def bootstrap_js() -> str:
|
|
|
423
434
|
|
|
424
435
|
|
|
425
436
|
def build_project(root: Path, out_dir: Path, config=None,
|
|
426
|
-
warnings: list[str] | None = None
|
|
437
|
+
warnings: list[str] | None = None,
|
|
438
|
+
release: bool = False) -> dict:
|
|
427
439
|
"""Full multi-page project build: browser pages/layouts/boundaries
|
|
428
440
|
(`build_pages`), the application shell, static-route HTML shells
|
|
429
441
|
(architecture decision §2), the runtime assets (`vlrt.js`, `vlrt.css`,
|
|
@@ -437,7 +449,8 @@ def build_project(root: Path, out_dir: Path, config=None,
|
|
|
437
449
|
when the project has no `app/**/index.vl` pages at all (nothing is
|
|
438
450
|
written — the caller falls back to the existing single-entry build).
|
|
439
451
|
`warnings`, when given, receives every build warning (per-file skipped
|
|
440
|
-
constructs and the styling warnings) so the caller can print them.
|
|
452
|
+
constructs and the styling warnings) so the caller can print them.
|
|
453
|
+
`release` selects the minified runtime files (`runtime_assets.py`)."""
|
|
441
454
|
out_public = out_dir / "public"
|
|
442
455
|
titles: dict[str, str] = {}
|
|
443
456
|
client_manifest = build_pages(root, out_public, config=config,
|
|
@@ -446,12 +459,9 @@ def build_project(root: Path, out_dir: Path, config=None,
|
|
|
446
459
|
return {}
|
|
447
460
|
|
|
448
461
|
out_public.mkdir(parents=True, exist_ok=True)
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
(out_public
|
|
452
|
-
(_ASSETS / "vlrt.css").read_text(encoding="utf-8"), encoding="utf-8")
|
|
453
|
-
(out_public / "vlrouter.js").write_text(
|
|
454
|
-
(_ASSETS / "vlrouter.js").read_text(encoding="utf-8"), encoding="utf-8")
|
|
462
|
+
# --release ships the committed minified runtime; see runtime_assets.py.
|
|
463
|
+
from .runtime_assets import write_runtime
|
|
464
|
+
write_runtime(out_public, ("vlrt.js", "vlrt.css", "vlrouter.js"), release)
|
|
455
465
|
(out_public / "bootstrap.js").write_text(bootstrap_js(), encoding="utf-8")
|
|
456
466
|
styled = (out_public / "app.css").is_file()
|
|
457
467
|
shell = shell_html(styled=styled)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Write the browser runtime files (`vlrt.js`, `vlrt.css`, `vlrouter.js`).
|
|
2
|
+
|
|
3
|
+
A plain build copies the readable, commented sources from `assets/`, as it
|
|
4
|
+
always has. A `--release` build copies the minified siblings
|
|
5
|
+
(`vlrt.min.js`, `vlrt.min.css`, `vlrouter.min.js`) under the plain names, so
|
|
6
|
+
`index.html` and the page shells do not change.
|
|
7
|
+
|
|
8
|
+
The minified files are generated by the maintainer tool in
|
|
9
|
+
`prototype/vlbuild/minify/` and committed. Nothing here runs a JS tool: a
|
|
10
|
+
user's build only copies bytes. Gate 2 checks the committed files against a
|
|
11
|
+
fresh minify, and the release copy is byte for byte, so what the gates test
|
|
12
|
+
is what ships.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from .emit import BuildError
|
|
20
|
+
|
|
21
|
+
ASSETS = Path(__file__).resolve().parent / "assets"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def minified_name(name: str) -> str:
|
|
25
|
+
stem, dot, ext = name.rpartition(".")
|
|
26
|
+
return "%s.min.%s" % (stem, ext)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def write_runtime(out_dir: Path, names: tuple[str, ...], release: bool) -> None:
|
|
30
|
+
for name in names:
|
|
31
|
+
if release:
|
|
32
|
+
src = ASSETS / minified_name(name)
|
|
33
|
+
if not src.is_file():
|
|
34
|
+
raise BuildError(
|
|
35
|
+
"the minified runtime %s is missing from this compiler "
|
|
36
|
+
"install; rebuild without --release, or reinstall volaro"
|
|
37
|
+
% src.name)
|
|
38
|
+
(out_dir / name).write_bytes(src.read_bytes())
|
|
39
|
+
else:
|
|
40
|
+
(out_dir / name).write_text(
|
|
41
|
+
(ASSETS / name).read_text(encoding="utf-8"), encoding="utf-8")
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
|
+
import os
|
|
5
6
|
import shutil
|
|
7
|
+
import stat
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
from urllib.parse import unquote, urlsplit
|
|
8
10
|
|
|
@@ -10,6 +12,57 @@ from vlcheck import ast_nodes as A
|
|
|
10
12
|
from .emit import BuildError, _walk
|
|
11
13
|
|
|
12
14
|
|
|
15
|
+
# A Windows reparse tag with this bit set names another file or directory:
|
|
16
|
+
# symbolic links, directory junctions (mount points) and similar redirects.
|
|
17
|
+
# Cloud-file placeholders (OneDrive) do not set it, so they still count as files.
|
|
18
|
+
_NAME_SURROGATE = 0x20000000
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _is_link(path: Path) -> bool:
|
|
22
|
+
"""True for a symbolic link or a Windows junction; neither is followed.
|
|
23
|
+
|
|
24
|
+
`Path.is_symlink()` is False for an NTFS junction, and `Path.is_junction()`
|
|
25
|
+
only exists from Python 3.12, so older Pythons read the reparse tag.
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
st = os.lstat(path)
|
|
29
|
+
except OSError:
|
|
30
|
+
return False
|
|
31
|
+
if stat.S_ISLNK(st.st_mode):
|
|
32
|
+
return True
|
|
33
|
+
is_junction = getattr(path, "is_junction", None)
|
|
34
|
+
if is_junction is not None and is_junction():
|
|
35
|
+
return True
|
|
36
|
+
return bool(getattr(st, "st_reparse_tag", 0) & _NAME_SURROGATE)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _asset_files(root: Path) -> list[Path]:
|
|
40
|
+
"""Every regular file under root, refusing links and never descending into one."""
|
|
41
|
+
files = []
|
|
42
|
+
pending = [root]
|
|
43
|
+
while pending:
|
|
44
|
+
folder = pending.pop()
|
|
45
|
+
for path in sorted(folder.iterdir()):
|
|
46
|
+
if _is_link(path):
|
|
47
|
+
raise BuildError(
|
|
48
|
+
f"E-ASSET-LINK: symbolic links and junctions are not supported in assets/: {path}")
|
|
49
|
+
if path.is_dir():
|
|
50
|
+
pending.append(path)
|
|
51
|
+
elif path.is_file():
|
|
52
|
+
files.append(path)
|
|
53
|
+
else:
|
|
54
|
+
raise BuildError(f"E-ASSET-PATH: assets/ supports regular files only: {path}")
|
|
55
|
+
# Defence in depth: whatever the checks above missed, every file must
|
|
56
|
+
# still resolve to somewhere inside the resolved assets/ root.
|
|
57
|
+
real_root = root.resolve()
|
|
58
|
+
for path in files:
|
|
59
|
+
real = path.resolve()
|
|
60
|
+
if real != real_root and real_root not in real.parents:
|
|
61
|
+
raise BuildError(
|
|
62
|
+
f"E-ASSET-LINK: {path} resolves outside assets/; symbolic links and junctions are not supported")
|
|
63
|
+
return sorted(files)
|
|
64
|
+
|
|
65
|
+
|
|
13
66
|
def prepare_assets(entry: Path, bundle: Path, emitter) -> tuple[Path, list[Path]]:
|
|
14
67
|
root = entry.parent / "assets"
|
|
15
68
|
dest = bundle / "assets"
|
|
@@ -17,23 +70,17 @@ def prepare_assets(entry: Path, bundle: Path, emitter) -> tuple[Path, list[Path]
|
|
|
17
70
|
if root.exists() and (dest.resolve() == root.resolve() or root.resolve() in dest.resolve().parents
|
|
18
71
|
or dest.resolve() in root.resolve().parents):
|
|
19
72
|
raise BuildError("E-ASSET-OUTPUT: output assets/ overlaps source assets/; choose another -o directory")
|
|
20
|
-
if dest
|
|
21
|
-
raise BuildError("E-ASSET-OUTPUT: output assets/ must not be a symbolic link")
|
|
73
|
+
if _is_link(dest):
|
|
74
|
+
raise BuildError("E-ASSET-OUTPUT: output assets/ must not be a symbolic link or junction")
|
|
22
75
|
if dest.exists() and not dest.is_dir():
|
|
23
76
|
raise BuildError("E-ASSET-OUTPUT: output assets/ must be a directory")
|
|
24
77
|
files = []
|
|
25
|
-
if root
|
|
26
|
-
raise BuildError("E-ASSET-LINK: assets/ must not be a symbolic link")
|
|
78
|
+
if _is_link(root):
|
|
79
|
+
raise BuildError("E-ASSET-LINK: assets/ must not be a symbolic link or junction")
|
|
27
80
|
if root.exists():
|
|
28
81
|
if not root.is_dir():
|
|
29
82
|
raise BuildError("E-ASSET-PATH: assets must be a directory beside the entry file")
|
|
30
|
-
|
|
31
|
-
if path.is_symlink():
|
|
32
|
-
raise BuildError(f"E-ASSET-LINK: symbolic links are not supported in assets/: {path}")
|
|
33
|
-
if path.is_file():
|
|
34
|
-
files.append(path)
|
|
35
|
-
elif not path.is_dir():
|
|
36
|
-
raise BuildError(f"E-ASSET-PATH: assets/ supports regular files only: {path}")
|
|
83
|
+
files = _asset_files(root)
|
|
37
84
|
# Check literal img references into this convention directory. Other URLs
|
|
38
85
|
# and dynamic strings retain their existing meaning; no guessing/rewrite.
|
|
39
86
|
for view in emitter.views:
|
package/language/crib.md
CHANGED
|
@@ -748,7 +748,7 @@ No `alt` is `E-IMG-ALT`. Put local files in `assets/` beside the entry file:
|
|
|
748
748
|
`img src:"assets/logo.svg" alt:"Logo"`. Build and dev copy the directory into
|
|
749
749
|
the served bundle; dev rebuilds on asset additions, edits and deletions.
|
|
750
750
|
A missing literal `assets/...` image is `E-ASSET-MISSING` at build time.
|
|
751
|
-
The output `assets/` directory is compiler-owned; source symlinks are rejected.
|
|
751
|
+
The output `assets/` directory is compiler-owned; source symlinks and junctions are rejected.
|
|
752
752
|
Other relative paths are not copied automatically; `data:` and external URLs work.
|
|
753
753
|
|
|
754
754
|
Every build that renders a page needs a module-level `page title:"…"` (becomes
|
|
@@ -801,8 +801,12 @@ A network failure, timeout or undecodable body has no `http.` code, so the
|
|
|
801
801
|
the browser, so only unauthenticated APIs work: `auth`, `retry`, `rate` and
|
|
802
802
|
`headers` directives fail the build (`E-API-DIRECTIVE-UNSUPPORTED`). `par` and
|
|
803
803
|
`spawn` are in the spec but not built; the build rejects them.
|
|
804
|
-
|
|
805
|
-
`
|
|
804
|
+
`auth.*` talks to this project's own `auth` block and server; an existing
|
|
805
|
+
endpoint such as `/api/login` goes through an `api` block (above). Built:
|
|
806
|
+
`auth.sign_in(email:, password:)`, `sign_up(email:, password:)`, `sign_out()`,
|
|
807
|
+
`reset_password(email:)`, `confirm_reset(token:, password:)`,
|
|
808
|
+
`confirm_verify(token:)`; any other name is `E-AUTH-METHOD`. Navigate with
|
|
809
|
+
`route.to("/path")`.
|
|
806
810
|
|
|
807
811
|
## Styling
|
|
808
812
|
|
package/language/spec.md
CHANGED
|
@@ -1652,6 +1652,8 @@ The client-side surface is eight calls. Each returns a fallible value with a doc
|
|
|
1652
1652
|
|
|
1653
1653
|
`start_oauth` takes a bare provider name matching a `provider oauth <name>` declaration in the `auth` block (`auth.start_oauth(google)`).
|
|
1654
1654
|
|
|
1655
|
+
The surface is closed: `auth.<name>` for any name outside this table is `E-AUTH-METHOD`, reported by the checker and the build alike, with the intended call suggested where one is likely (`auth.login` → `auth.sign_in`). `auth.*` reaches only this project's own `auth` block and its compiler-owned endpoints; an existing HTTP endpoint such as `/api/login` is called through an outbound `api` block (§9).
|
|
1656
|
+
|
|
1655
1657
|
---
|
|
1656
1658
|
|
|
1657
1659
|
## 12. Concurrency
|
|
@@ -2536,5 +2538,6 @@ hex digits representing a Unicode scalar value; surrogates are invalid.
|
|
|
2536
2538
|
The prototype copies the entry file's sibling `assets/` directory into the
|
|
2537
2539
|
browser bundle at `assets/` for both build and dev. Literal image references
|
|
2538
2540
|
inside this directory must exist at build time; dynamic URLs are not checked.
|
|
2539
|
-
The directory contains public files only, cannot contain symbolic links
|
|
2540
|
-
is copied without transformations. Output `assets/` is
|
|
2541
|
+
The directory contains public files only, cannot contain symbolic links or
|
|
2542
|
+
Windows junctions, and is copied without transformations. Output `assets/` is
|
|
2543
|
+
compiler-owned.
|
package/language/supported.md
CHANGED
|
@@ -388,6 +388,8 @@ audio video source track picture map area
|
|
|
388
388
|
bare call is `E-METHOD-AS-FN`. The spec's `sorted`, `first`, `fold`,
|
|
389
389
|
`merge` and `to_int` free functions are not built; a call to one in a view
|
|
390
390
|
or route is `E-FN-NOT-BUILT`.
|
|
391
|
+
- `try call()?` is a warning (`W-TRY-PROPAGATE`): the `catch` arms already
|
|
392
|
+
handle the error, so the `?` does nothing. Drop it.
|
|
391
393
|
|
|
392
394
|
### Data / services
|
|
393
395
|
- `service` with `db.<model>` query builder (`where` / `order` / `limit` /
|
|
@@ -401,6 +403,10 @@ audio video source track picture map area
|
|
|
401
403
|
the build, at any nesting depth. Use `?` to propagate errors.
|
|
402
404
|
- Password auth: `auth.sign_up` / `sign_in` / `sign_out` / `reset_password` /
|
|
403
405
|
`confirm_reset` / `confirm_verify` against compiler-owned endpoints.
|
|
406
|
+
Any other `auth.` name is `E-AUTH-METHOD` in `volaro check` and the build,
|
|
407
|
+
with the likely call suggested (`auth.login` -> `auth.sign_in`). The spec's
|
|
408
|
+
`auth.verify_totp` and `auth.start_oauth` pass the check but fail the build
|
|
409
|
+
with `E-AUTH-UNSUPPORTED`: TOTP and OAuth are not built.
|
|
404
410
|
- Outbound `api` blocks → a typed `fetch` client. `base` and each endpoint's
|
|
405
411
|
`path` are joined with exactly one `/` between them, so `base "/"` (or no
|
|
406
412
|
`base`) with `path "/api/login"` calls `/api/login` on the page's own
|
|
@@ -482,8 +488,8 @@ Dev uses the same build path and watches asset additions, edits and deletions.
|
|
|
482
488
|
Refresh the browser after a rebuild; this does not add automatic browser reload.
|
|
483
489
|
|
|
484
490
|
Literal image URLs into `assets/` are checked before output is written.
|
|
485
|
-
Missing files fail with `E-ASSET-MISSING`; traversal paths
|
|
486
|
-
are rejected. Query strings and URL-encoded filenames are supported. Dynamic
|
|
491
|
+
Missing files fail with `E-ASSET-MISSING`; traversal paths, symbolic links and
|
|
492
|
+
Windows junctions are rejected (`E-ASSET-LINK`). Query strings and URL-encoded filenames are supported. Dynamic
|
|
487
493
|
URLs are not existence-checked, but all files in the directory are copied.
|
|
488
494
|
All regular files in `assets/`, including dotfiles, are public: put only intended
|
|
489
495
|
public resources there. Output `assets/` is compiler-owned and replaced on each
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "volaro",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.8",
|
|
4
4
|
"description": "Volaro \u2014 an application language written to be authored by an AI agent and read by a person. Ships the language reference and a working compiler for a supported subset (volaro check / build / dev). CLI: `volaro` (alias `vl`).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
package/scripts/selftest.mjs
CHANGED
|
@@ -43,11 +43,30 @@ try {
|
|
|
43
43
|
check("build wrote app.js", existsSync(join(out, "app.js")));
|
|
44
44
|
check("build wrote vlrt.js", existsSync(join(out, "vlrt.js")));
|
|
45
45
|
|
|
46
|
+
// --release copies the minified runtime the package ships; no JS tool runs.
|
|
47
|
+
const rel = join(dir, "release");
|
|
48
|
+
const rbld = spawnSync(node, [vl, "build", src, "-o", rel, "--release"], { encoding: "utf8" });
|
|
49
|
+
check("vl build --release exits 0", rbld.status === 0, (rbld.stderr || "").trim());
|
|
50
|
+
const assets = join(pkgRoot, "compiler", "vlbuild", "vlbuild", "assets");
|
|
51
|
+
check("build --release ships the minified runtime",
|
|
52
|
+
["vlrt.js", "vlrt.css"].every((n) => existsSync(join(rel, n))
|
|
53
|
+
&& readFileSync(join(rel, n)).equals(readFileSync(join(assets, n.replace(/\.(js|css)$/, ".min.$1"))))));
|
|
54
|
+
|
|
46
55
|
const bad = join(dir, "bad.vl");
|
|
47
56
|
writeFileSync(bad, "view Broken(\n h1 \"unterminated\n");
|
|
48
57
|
const badChk = spawnSync(node, [vl, "check", bad], { encoding: "utf8" });
|
|
49
58
|
check("vl check exits non-zero on broken source", badChk.status !== 0);
|
|
50
59
|
|
|
60
|
+
// A diagnostic's source frame uses box-drawing characters. Windows gives a
|
|
61
|
+
// redirected Python stdout a legacy code page (cp1252), which cannot
|
|
62
|
+
// encode them; PYTHONIOENCODING=ascii reproduces that on every OS.
|
|
63
|
+
const narrow = spawnSync(node, [vl, "check", bad],
|
|
64
|
+
{ encoding: "utf8", env: { ...process.env, PYTHONIOENCODING: "ascii" } });
|
|
65
|
+
const narrowOut = (narrow.stdout || "") + (narrow.stderr || "");
|
|
66
|
+
check("vl check prints its diagnostic through a non-UTF-8 stdout",
|
|
67
|
+
!narrowOut.includes("UnicodeEncodeError") && narrowOut.includes("error[E-") && narrowOut.includes("┌─"),
|
|
68
|
+
narrowOut.trim().split("\n").pop());
|
|
69
|
+
|
|
51
70
|
// `volaro routes <project>`: page-server route manifest inspection
|
|
52
71
|
const posts = join(dir, "app", "posts");
|
|
53
72
|
mkdirSync(posts, { recursive: true });
|
package/scripts/sync-compiler.sh
CHANGED
|
@@ -39,6 +39,18 @@ copy() {
|
|
|
39
39
|
copy "$src_validator" "$dest/validator/vlcheck"
|
|
40
40
|
copy "$src_vlbuild" "$dest/vlbuild/vlbuild"
|
|
41
41
|
|
|
42
|
+
# `--release` builds copy the committed minified runtime (assets/*.min.*).
|
|
43
|
+
# Refuse to pack if it is stale against a fresh minify from the pinned
|
|
44
|
+
# maintainer tool, so the tarball ships the bytes the gates tested. The tool
|
|
45
|
+
# itself (prototype/vlbuild/minify) is never copied into the package.
|
|
46
|
+
minify="$repo/prototype/vlbuild/minify"
|
|
47
|
+
if [ ! -d "$minify/node_modules/terser" ]; then
|
|
48
|
+
echo "sync-compiler: the runtime minifier is not installed; run from the repo root:" >&2
|
|
49
|
+
echo " npm ci --ignore-scripts --no-audit --no-fund --prefix prototype/vlbuild/minify" >&2
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
node "$minify/minify-runtime.mjs" --check >&2
|
|
53
|
+
|
|
42
54
|
# Styling scripts only — never node_modules. Unstyled builds (the MVP path)
|
|
43
55
|
# do not touch this; styled builds need a one-time `npm ci` inside it.
|
|
44
56
|
copy "$src_styling" "$dest/vlbuild/styling"
|