- __mysql.cpp: <sql> never checked mysql_real_query's own return code,
so a genuine query failure was indistinguishable from "ran fine, zero
rows" - <try>/<catch> around a failing <sql> never actually caught
anything anywhere. Now throws on a real failure.
- spa.js: dynamically re-injected scripts now run in true sequential
order (wait for each external script's load event before running the
next), fixing checkout's Square card field never building on the
first client-side navigation to /checkout (only after a refresh) -
async=false alone only orders multiple external scripts relative to
each other, it doesn't delay a plain inline sibling.
- /vst split into a real overview + /vst/modular1 subpage with a left
menu, reusing opensource.page's existing subpage-tier pattern
(opensource.menu/main.css's submenuitem-active highlighting, and
several other subpage-tier retirements, were already finished earlier
this session but never committed - included here too).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
listMessages() only ever fetched everything then kept the newest
limit (default 50) messages, with no offset parameter and no way for
the client to know how many messages existed beyond what it was
shown - older mail was completely unreachable through the webmail UI.
__imap::listMessages() now takes an offset (counted back from the
newest message) and returns {"total":N,"messages":[...]} instead of
a bare array, so the client can page backward/forward and know when
it has reached either end. __imaplist tag and /email/list both pass
offset through; /email/search is unchanged (search results are not
paginated). email.js tracks currentOffset (reset alongside
currentFolder/currentUid at every existing reset site), and renders
Prev/Next controls next to the unread count showing the current
1-based range and total.
htmleditor.page previously used hardcoded blue/gray Windows-95-era
inline styles (#678, #abc, #808080, #def) totally disconnected from
the site current dark/gold Bond theme (main.css --bond-* variables) -
this is what showed up as "the old page with the blue stylesheet"
when opening a file to edit. Restyled the window chrome, toolbar,
title bar, and buttons to use the same custom properties/.button
class the rest of the site uses; left the #mainpage content-preview
canvas white since it renders the actual page being edited.
email.js: removed the Disconnect button/handler and its unused icon
from the email toolbar per request. Bumped its cache-buster in
admin-email.subpage.
New __imap*/__smtpsend/__eval Jet tags give templates real IMAP
(list/message/flag/delete/move/search/folders/accounts) and SMTP-send
capability, backing the new /email webmail client (email.jet/
email.menu, assets/images/email.js + newemail.js) and its
mainpagetemplate.jet-based page shell. Most of this tag family's
source was already being built into the live site (Tag.cpp already
registered __imap/__imaplist/__imapmessage/__imapflag/__imapdelete/
__eval) but had never actually been committed; this also registers
and commits the remaining five (__imapmove/__imapfolders/
__imapsearch/__smtpsend/__imapaccounts).
admin.page/admin.menu gain a real subpage tier (admin-email,
admin-newemail, admin-invoices, admin-products, admin-users) replacing
several previously top-level pages, mirrored by a similar
opensource.page subpage tier (opensource-overview/asm1802/barantmail/
jet/serverframework/synthmax) - old top-level pages retired via git
rename (asm1802.page, jet.page, serverframework.page,
admin_invoices.page); barantmail.page is dropped outright, folded into
opensource-barantmail.subpage.
New storefront (store.page, store-createstore.subpage,
barantstore.page) and cart flow (cart.jet, updatecartqty.proc,
removecartitem.proc, editproduct.page/selectproduct.proc/
update_product.proc for inventory maintenance against the barant
database).
New document upload/download manager (documents.jet, documents_api.jet,
documents_upload.proc, documents_delete.proc,
assets/images/documents.js + documents-page.js) with a persistent
floating panel, and a rich-text page editor (htmleditor.jet/.page,
createhtmleditor.proc, savehtmleditor.proc, assets/images/htmleditor.js
+ its icon/logo assets).
SESSIONCTL.tag: login now redirects back to the originally-requested
page instead of always landing on /account ($[start:0] vs $[start]
variable-name mismatch).
main.css/menu.bar/invoices.menu/jet_overview.section/login.menu:
styling and navigation updates for the above; new site assets (fonts,
images, audio) referenced by them.
jetc/build-assets.sh, list_jet_files.sh, lowercase_jet.py,
strip_fragments.py: small dev tooling used while building the above.
JetCore engine (Tag.cpp/.h, Global.cpp/.h, __*.cpp tag classes):
- Numerous __mysql.cpp connection-pooling correctness fixes (stale
result sets, double-indirection sessionid bugs, field caching,
pool keying) and a session-registration-before-connection-confirmed
ordering fix (dangling pointer on a failed connect)
- Tag.cpp: large body of fixes to the tag-dispatch/container-scanning
logic (bare-name fallback, database aliasing, single-quote handling,
@-prefix session variable resolution, TAGDEF caching, nested split-tag
scanning for if/while/until/ifrow so an inner one doesn't corrupt an
outer one's own boundary scan) plus the process-first rendering
order fix (a <process> now runs before <checksession>/page
resolution, fixing a real "blank page after successful login" bug)
- Global.h/.cpp: added imapSessions (parallel to the existing __mysql*
session map) for the new <imap> tag family
- __whiledir.cpp/__include.cpp: caching and directory-name-aliasing
fixes
- __call.cpp/.h: fixed a dangling-pointer bug
- __write.cpp: fixed an O_WRONLY-only open that couldn't create a new
file
- __ifrow.cpp/__sql.cpp: fixed the same sessionid double-indirection
bug __mysql.cpp had (resolveKeyword's result was being used as a map
key lookup instead of the resolved value itself)
jetc compiler (jetc/JetParser.cpp/.h, JetCodegen.cpp/.h, JetAST.h,
jetc.cpp, build-shared.sh, build-compiled.sh):
- Parser fixes: inline <include>, nested-quote handling, void-tag
recognition
- Codegen fixes and expanded AST support
- build-shared.sh: atomic rename into place for a freshly-built .jtc
(a live server dlopen()ing the same path mid-rebuild was corrupting
its own already-mapped view of the old file) and a staleness check
so a cached JetCore .o doesn't silently keep serving yesterday's
fixes forever
- Added -lssl to both build scripts' link lines (needed once the new
<imap> tag's client-side TLS code existed - previously nothing in
this codebase used OpenSSL's SSL/TLS client API, only libcrypto)
Site content (jetc/barant-site/): SPA (fetch-and-swap navigation)
rollout across essentially every .page/.jet/.section/.tag/.proc/.method
file - dynamic-content fixes, fragment-mode stripping, native
$[@name] session variables replacing older mechanisms, and this
session's own checkout.page redesign, login start-param redirect fix,
and per-page side-menu (menu.bar/$[PAGE].menu) refresh support.
Completed the earlier main.css/control.css move from assets/ to
assets/images/ (git still showed the old paths as tracked-but-deleted
until now).
Deliberately left out of this commit: dozens of .pre-<description>-
<date> manual backup files (this session's and earlier ones' own
before/after snapshots, not meant for permanent history), compiled
.jtc build outputs, ad hoc test binaries and scripts under jetc/
(login_asan, wcwc_test, optest2_bin, etc.), and jetc/__pycache__/ -
none of these are meant to be tracked; a .gitignore covering them
would be worth adding separately.
Three correctness fixes to the --shared build path:
- Only skipped rebuilding a .pic.o when it existed at all, never checking
whether the source .cpp was newer - confirmed live: Tag.pic.o (and 6
other JetCore objects) sat uncompiled since 2026-08-25 while Tag.cpp
itself picked up same-day fixes, so every .jtc silently ran yesterday's
JetCore code.
- Only re-archive libCoreUtils_pic.a when a member object actually changed,
instead of unconditionally on every build.
- Link to a temp file in the same directory, then rename() it into place,
instead of linking directly onto the live .jtc path. A running APIServer
may already have that exact path dlopen()ed (hot-reload watches this
directory) - overwriting it in place corrupts the live mapping's code
pages out from under the process, causing a dlclose() SIGSEGV the next
time it reloads. rename() never touches the old inode, so anything with
it still mapped keeps seeing the unmodified original.
Its source uses <whiledir dir=... name=...>, but the tags real keyword is
path= (confirmed in __whiledir.cpp) - dir/name are not recognized, so this
throws whiledir tag must specify a path. at runtime, same as barant.com own
mismatched source would. Converted through the normal pipeline rather than
omitted, same treatment as vst/modular1/modular1_patches: verified it fails
with a clean HTTP 500 via APIServers existing exception-catching path, and
the server stays alive and responsive throughout.
<database> was SESSIONCTL.tag's own JET-1.4-only concept - the new JetCore
port never implemented it. The real, dispatched tag for opening a MySQL
connection is <mysql> (confirmed in Tag.cpp's dispatch chain), so these 3
product pages - the ones deferred in the previous conversion pass because
they pull live rows via <sql> - now wrap their content in a real
<mysql host="localhost" database="barant" user="barant" password="...">
matching barant.com's own config.cfg, with <retain> (a SESSIONCTL-only
form-echo helper with no analog here) stripped.
host="localhost" is correct per config.cfg but only reachable from
barant.com itself - a locally-run .jtc genuinely cannot connect from here.
That's intentional: the connection fails at runtime exactly as if the
database were down, and APIServer's existing catch(coreutils::Exception)
path (ApiSession.cpp) already turns that into a clean 500 rather than a
crash - verified live: all 3 pages return HTTP 500 with mysql's own
"database and host parameters are not valid." message, and the server
process stays up and responsive throughout.
The real DB password is deliberately not a literal in this file - it's
read from BARANT_DB_PASSWORD at conversion time, and the resulting .jet
output for these 3 pages is gitignored rather than committed, so the
credential never enters git history even though it does end up baked into
the locally-generated, locally-deployed .jtc.
Pulls .page files and shared template infrastructure (mainpagetemplate.temp,
menu.bar, barant.tag, opensource.menu, cart.menu, login.menu) read-only from
barant.com via SFTP into jetc/barant-site/src, then convert.py mechanically
flattens them into self-contained .jet files: linkbutton becomes a plain
anchor link (the old SESSIONCTL PAGE/PROCESS form-postback model has no
equivalent here - each page is its own real #api route now, not a shared
single-form state machine), sectionframe is expanded per its barant.tag
definition, /images/* references are fetched and inlined as data URIs
(APIServer has no static-file serving), and session-gated chrome (cart,
account/admin, login.menu) is resolved to its anonymous-visitor default
state, since no session/DB backend is wired into this conversion.
17 pages convert and serve cleanly end-to-end (verified via a local
apiserver-barant-site.json instance on :8081, kept separate from the
existing demo server on :8080 to avoid a /main route collision with the
earlier hand-cloned barant.jet). Two pages deliberately out of scope for
this pass: davidarant.page (whiledir filesystem iteration) and the
per-topic sub-pages of jet.page (dispatches on an ARGS query param this
conversion does not populate - only the default overview state converts).
vst.page/modular1.page/modular1_patches.page also deferred - they pull
live product rows via sql, a real backend dependency beyond "pages and
their template" scope.
Nothing was written back to barant.com - SFTP access was read-only
throughout, per explicit instruction to convert locally without touching
the original site.
The two <img> tags used relative /images/... paths, which 404 through
APIServer (no static-file serving) exactly like the stylesheets did.
The three @font-face src urls were absolute https://barant.com/fonts/...
references - technically reachable, but barant.com does not send an
Access-Control-Allow-Origin header for them, so browsers block loading
them cross-origin from a different serving host. Fetched all five and
embedded as base64 data: URIs so the served page is fully self-contained.
APIServer has no static-file serving, only #api-declared routes, so the
original relative /css/main.css and /css/control.css <link> tags 404d.
Fetched both stylesheets from barant.com and embedded them directly in
a <style> block instead.
Fetched the live raw HTML from https://barant.com/ (rendered by the
user's own separate JET-1.4 C interpreter, unrelated to this JetCore/
jetc project - see the barant.com project memory) and embedded it as
literal content in a jetc template (#api GET /barant), as a real-world
stress test of the --shared compiler against markup nobody wrote with
jetc in mind.
Needed several fixes to compile, all discovered by reading jetc's own
JetParser.cpp rather than guessing, and each one either genuinely
DOM-neutral or an actual bug fix in barant.com's own markup:
- Void elements (<meta>/<link>/<input>/<img>/<br>) had to be
self-closed. jetc's parser treats any unrecognized <tag ...> as a
generic custom tag needing a matching </tag> (mirroring the
interpreter's own extensible custom-tag model) - it has no built-in
notion of HTML's void-element list. Self-closing is standard HTML5
syntax with zero effect on the parsed DOM either way.
- <!DOCTYPE html> and HTML comments (<!--...-->) aren't understood at
all - confirmed via a minimal repro that even a bare <!-- comment -->
breaks tag tokenization (the "<!" prefix isn't special-cased).
Routed the doctype through a <set name="x" value="<!DOCTYPE html>"/>
+ $[x] pair instead - a <set> attribute value is a plain quoted
string, never re-parsed as nested markup, verified via host_test to
render byte-identical to the literal text. The one HTML comment
(a Google Analytics label) was just removed outright - comments have
no rendered/DOM effect, so this is neither a workaround nor a
meaningful change, and the actual <script> tags it was labeling are
untouched and verified still present.
- A genuine real bug in barant.com's own live markup: `<table
class="menubar" cellspacing="0" cellpadding="0" width: 100%">` (line
28) - a stray unpaired `"` with no matching partner (missing `=` and
opening quote before `100%`). jetc's attribute scanner toggles into
"inside a quoted value" mode on any `"` and treats everything
(including the real closing `>`) as opaque until the next `"` - a
single unpaired quote anywhere derails tokenization for the rest of
the file, which is exactly what "unterminated tag near end of input"
turned out to mean here. Browsers silently tolerate this malformed
attribute (hence it looking fine live); jetc's simpler tokenizer
doesn't. Fixed to width="100%", the obvious intent.
- Two stray trailing </if></if> lines (a leftover artifact from
barant.com's own JET-1.4 template engine, unrelated to anything here)
were dropped - dead, invisible in any browser, and incompatible with
jetc's tag-matching (no corresponding <if> anywhere).
Verified: diffed the actual rendered output (via host_test) against the
original fetched HTML - every difference maps exactly to one of the
changes above, nothing else moved. Deployed live into the running
APIServer test instance (GET /barant) and confirmed Content-Length
matches the diffed render exactly (13944 bytes).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Purely a naming-convention change per user request, not a format
change - the output is still a real ELF shared object (-shared -fPIC),
still dlopen()-able exactly as before. .jtc ("jet compiled") marks it as
an APIServer-loadable content module rather than a generic library.
build-shared.sh: usage text and STEM derivation
(basename "$OUTPUT" .so -> .jtc) both updated - the STEM change is the
one functional piece here, everything else in this commit is comments/
usage strings. Doc comments in jetc.cpp/JetCodegen.h/host_test.cpp/
api_test.cpp updated to match.
Verified live: rebuilt jetc and all five demo*.jtc modules, reran
api_test/host_test against demo3.jtc (still resolves jetc_api_demo3/
jetc_render_demo3 and renders correctly) - the extension is purely
cosmetic to dlopen(), as expected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Used to live-verify APIServer end to end - one small template per
distinct code path, matching demo.jet/demo2.jet's existing role as
worked examples rather than throwaway scratch files:
- demo4.jet: "#api GET /users/{id}" + $[id] - path param capture.
- demo5.jet: "#api POST /echo" + $[:message] - urlencoded form body via
CGIFormData, reached through the same $[:name] CGI convention the
interpreter already uses.
- demo6.jet: "#api GET /crash" + $[:message] with no body present -
deliberately throws (Tag::getVariable's cgiData-is-null error),
verifying a render-time exception turns into a clean response instead
of taking the server down.
- demo7.jet: "#api GET /search" + $[q] - query string parsing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Stage 1 of the APIServer plan: a compiled --shared .so currently exports
only a bare jetc_render_<name>(Global&, MString&) with no way for a host
to discover what HTTP method+path it wants to serve. Adds a leading
"#api METHOD /path" directive (one or more, parsed the same way jetc
already strips a leading "#!" shebang line - not modeled as a new
AST/tag-attribute concept, since jetc's parser has no existing notion of
the enclosing <jet> tag's attributes to hang this off of).
In --shared mode, JetCodegen now also emits a second exported function
alongside the render entry point:
struct JetApiRoute { const char *method; const char *path; };
extern "C" const JetApiRoute *jetc_api_<name>(int *count) { ... }
<name> is derived the same way for both symbols (sanitizeIdent(
baseNameNoExt(inputPath))), so a future host loader can find both by
convention from just the .so's filename - no name needs to be embedded
in the metadata itself, and the routes a module declares can never drift
out of sync with the render function that implements them (the failure
mode a sidecar manifest file would have).
Malformed directives (missing method/path, path without a leading '/')
are a hard compile error; #api directives present without --shared print
a non-fatal warning (the metadata has nowhere to go without a host to
load a .so, but the render-only executable/CGI path still works fine).
Verified live, not just compiled: rebuilt jetc, compiled demo3.jet (a new
worked example with two #api lines sharing one handler) via
build-shared.sh into a real demo3.so, and added api_test.cpp (mirrors
host_test.cpp's dlopen()/dlsym() pattern) confirming jetc_api_demo3
returns the expected two routes at runtime. Re-ran host_test against the
same .so to confirm the render path is unaffected. Confirmed demo.jet/
demo2.jet (no #api lines) emit no JetApiRoute code at all - no regression
for templates that don't declare routes.
Also fixed a gitignore gap surfaced while committing this: an earlier
"jetc/*.cpp" ignore rule (meant for generated *.compiled.cpp output) was
a blanket pattern that silently hid this commit's own api_test.cpp from
git status. Narrowed to "jetc/*.compiled.cpp" (the actual generated-file
naming, per build-compiled.sh/build-shared.sh) and added explicit ignores
for the host_test/api_test/demo3 binaries plus two known stale generated
leftovers (demo.cpp/demo2.cpp) that predate this session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
For the server you're about to build: `jetc --shared template.jet out.cpp`
now emits no main() at all - instead the render logic is exported as
`extern "C" void jetc_render_<template>(jet::Global &global,
coreutils::MString &out)`, one call per request, meant to be dlsym()'d
by a long-running host process instead of spawning a jet-2.0 process (or
even a separate compiled executable) per request. extern "C" here only
fixes the symbol's name mangling for a stable dlsym() target - the
parameter types are still real C++ classes, safe to pass across the
dlopen() boundary since the host and the .so share the same
CoreUtils/JetCore headers and toolchain (a same-ABI plugin boundary, not
a foreign C one). coreutils::Exception propagates out of the call
uncaught, so the host decides how to handle a render failure rather than
the module unilaterally deciding (the executable mode's try/catch prints
to stdout and exits, which is CGI-appropriate but wrong for a persistent
server). The exported symbol name is derived from the input filename
(jetc.cpp's baseNameNoExt+sanitizeIdent) so multiple compiled templates'
.so's stay collision-free even under dlopen(..., RTLD_GLOBAL).
jetc/build-shared.sh builds the .so: JetCore's 22 tag classes are still
force-linked directly (Tag::interpretSpan's fallback path can dispatch
to any of them at runtime, regardless of what a given template
statically references), but CoreUtils objects are compiled -fPIC and
archived into a throwaway libCoreUtils_pic.a instead of being linked
directly - libCoreUtils.a itself isn't position-independent, so linking
it into a .so fails outright ("relocation ... can not be used when
making a shared object"), and force-linking every CoreUtils .cpp
directly (rather than archiving, so the linker only pulls in what's
actually referenced) surfaced an unrelated, pre-existing incomplete
class - IMFMessage's vtable is never emitted anywhere, presumably a
partially-migrated class (see project memory on IMFMultipart/IMFText
still being fd-based) - as an unresolved dlopen()-time symbol, even
though nothing here uses it. A normal archive-based link (like the
existing libCoreUtils.a already does for jet-2.0) never pulls IMFMessage
in at all, so building libCoreUtils_pic.a the same way sidesteps it
without touching the unrelated bug itself.
jetc/host_test.cpp is a minimal stand-in for the real server: dlopen()s
a compiled .so, dlsym()s its entry point, and calls it twice with fresh
jet::Global objects (simulating two separate requests against one loaded
module) to prove the .so is genuinely reusable, not single-use. Verified
both demo templates (demo.jet, demo2.jet - the latter covering nested
if/for, a stateful while loop, and the interpretSpan fallback path) build
as .so's and produce the same byte-identical output as the executable
and interpreter paths, across two calls each. Also re-verified the
existing (non-shared) executable build path still matches jet-2.0's
output after the Codegen::generate() signature change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
jetc (JetCore/jetc/) compiles a subset of JET template syntax - literal
text, $[...]/#[...] variable output, <set>, <if>/<else>, <for>, <while> -
into a standalone C++ program that links against CoreUtils and the same
JetCore tag-class library jet-2.0 itself uses. The static tag/text
structure becomes real C++ control flow (if/for/while), resolved once at
build time instead of being re-scanned character-by-character from raw
source on every request and, for loop bodies, on every single iteration
(confirmed by reading Tag::parseContainer - it re-tokenizes its container
text from scratch every time it runs, since a tag's parse and its
execution are the same pass in this codebase). Anything genuinely
dynamic - variable values, expr/value comparisons, loop bounds - still
calls into the existing runtime (Tag::getVariable/resolveKeyword,
jet::Operand) exactly as the interpreter does; compiling doesn't try to
eliminate that; only the repeated re-parsing of the template's own shape.
A <if type="..."> or <while type="..."> comparison type is resolved to
its C++ operator once at compile time (type is a literal attribute value
in practice, not runtime data) instead of being re-checked against six
string constants on every evaluation.
Any tag jetc doesn't have a codegen rule for (mysql, sql, call, include,
a <set> with a container body, etc.) is captured as its exact raw source
span and handed to the existing interpreter at compiled-program runtime
via the new Tag::interpretSpan(), so an unsupported construct degrades
gracefully instead of failing to compile - a real, if narrower, program
rather than requiring full tag coverage up front.
Tag.h/.cpp: made assignVariable public (parallel to the already-public
getVariable - generated code needs a write path without being a Tag
subclass) and added interpretSpan() as the fallback's entry point into
parseContainer.
Verified live: two representative templates (jetc/demo.jet, an if/for/set
walkthrough; jetc/demo2.jet, a nested <if> inside a <for>, a <while> that
mutates and rereads its own condition variable across iterations, and a
fallback-interpreted <comment> tag) both compile, link, run standalone,
and produce byte-identical output to jet-2.0 running the same source.
Building and running jetc/demo2.jet's <while> loop surfaced a real,
previously-unknown MString bug (fixed separately, see CoreUtils commit
6e4e463) along with a separate, already-existing bug in __while.cpp's
value1/value2/type form (unrelated to this compiler, not fixed here -
see project memory).
jetc/build-compiled.sh builds the compiler and a given .jet file's
compiled binary in one step; jetc/demo.jet and demo2.jet are kept as
worked examples. Known limitations: <set> with a container body isn't
modeled (falls back to interpretation); a fallback-interpreted tag
nested inside compiled control flow uses the template's single top-level
scope for parent/local context rather than a faithful per-level chain,
which only matters for legacy tags that rely on nested "local" scoping.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>