code wiki / _hdl_build / nx_sitegen.nx

nx_sitegen.nx source

↩ module page · 143 lines · 7622 B

1// nx_sitegen.nx -- THE AUTO-BUILDER ENGINE: build a whole site page from a DECLARATIVE BLUEPRINT (data), 2// deterministically, with NO AI and NO hand-written HTML. A blueprint is an ordered array of typed BLOCKS; 3// the engine walks them in order and dispatches each to the sovereign nx_web_builder emitter for that kind. 4// Because the blueprint is pure DATA: 5// (1) a site is portable / diffable / versionable and NOT trapped in a proprietary editor (Wix/Webflow 6// exceed angle -- their layout lives in a closed cloud store; ours is data the operator owns), 7// (2) the Nishi team can REBUILD or UPDATE a site with no human and no model in the loop (autonomy), 8// (3) a local LLM is OPTIONAL -- its ONLY job is to emit a blueprint; the build + serve path stay 9// deterministic + sovereign whether the blueprint came from a human, the team, or a model. 10// The engine REFUSES (emits nothing, returns -1) any blueprint that is not UX-compliant by construction 11// (wb_ux_compliant: a clear CTA + onsite search + trust signals + <=3 resolution steps + responsive) -- 12// a guarantee the Wix/WordPress "anything-goes canvas" does NOT make. Block kinds map 1:1 to nx_web_builder 13// emitters; new site types (game-publish, video-gallery) add new kinds WITHOUT touching the spine 14// (extensible; unknown kinds are skipped gracefully = forward-compatible). Composes nx_web_builder. 15// license_tier: ORIGINAL 16import "nx_web_builder.nx" 17import "nx_html_sanitize.nx" 18import "nx_syscalls.nx" 19 20// ---- block-kind vocabulary (the data-driven seam, rule 11; grows per site type) ----------------- 21const SG_HEADER: i64 = 1 // a=brand b=cta_label c=cta_href 22const SG_HERO: i64 = 2 // a=headline b=sub c=cta_label d=cta_href 23const SG_SEARCH: i64 = 3 // a=action b=placeholder 24const SG_CARDS: i64 = 4 // a=heading list=[title0,body0,title1,body1,...] lcount=#cards 25const SG_STEPS: i64 = 5 // a=heading list=[label0,label1,...] lcount=#steps (<=3) 26const SG_TRUST: i64 = 6 // list=[sig0,sig1,...] lcount=#signals 27const SG_FOOTER: i64 = 7 // a=text 28const SG_FAQ: i64 = 8 // a=heading list=[q0,a0,q1,a1,...] lcount=#qa pairs (additive content) 29const SG_GALLERY: i64 = 9 // a=heading list=[src0,alt0,...] lcount=#images (additive content) 30const SG_CTA: i64 = 10 // a=heading b=sub c=label d=href (mid-page call-to-action banner) 31 32// derive the UX signals from the blueprint itself, then ask the baked-in (research-grounded) gate. 33func sg_compliant(kind: *i64, b: *i64, c: *i64, lcount: *i64, n: i64) -> i64 { 34 var has_cta: i64 = 0 35 var has_search: i64 = 0 36 var n_trust: i64 = 0 37 var n_steps: i64 = 0 38 var i: i64 = 0 39 while i < n { 40 let k: i64 = kind[i] 41 if k == SG_HEADER { if b[i] != 0 { has_cta = 1 } } 42 if k == SG_HERO { if c[i] != 0 { has_cta = 1 } } 43 if k == SG_SEARCH { has_search = 1 } 44 if k == SG_TRUST { n_trust = n_trust + lcount[i] } 45 if k == SG_STEPS { n_steps = lcount[i] } 46 i = i + 1 47 } 48 return wb_ux_compliant(n_steps, has_cta, has_search, n_trust) 49} 50 51// the UX grade (0-100) a blueprint would earn (responsive always on, baked into wb_doc_open). 52func sg_grade(kind: *i64, b: *i64, c: *i64, lcount: *i64, n: i64) -> i64 { 53 var has_cta: i64 = 0 54 var has_search: i64 = 0 55 var n_trust: i64 = 0 56 var n_steps: i64 = 0 57 var i: i64 = 0 58 while i < n { 59 let k: i64 = kind[i] 60 if k == SG_HEADER { if b[i] != 0 { has_cta = 1 } } 61 if k == SG_HERO { if c[i] != 0 { has_cta = 1 } } 62 if k == SG_SEARCH { has_search = 1 } 63 if k == SG_TRUST { n_trust = n_trust + lcount[i] } 64 if k == SG_STEPS { n_steps = lcount[i] } 65 i = i + 1 66 } 67 return wb_ux_grade(n_steps, has_cta, has_search, n_trust, 1) 68} 69 70// escape an untrusted blueprint text field for safe HTML insertion (the boundary defense, rule 12 -- a 71// blueprint can come from a file, a typo, or a model, so the engine NEVER trusts field bytes). Returns an 72// escaped, null-terminated copy. A null/empty field -> "" (also guards against a null deref in wb_*). 73func sg_esc(src: *u8) -> *u8 { 74 if (src as i64) == 0 { return "" as *u8 } 75 var sl: i64 = 0 76 while src[sl] != (0 as u8) { sl = sl + 1 } 77 let cap: i64 = sl * 8 + 16 // entity expansion is bounded (<=6x); 8x is safe headroom 78 let dst: *u8 = sys_mmap(cap) 79 let n: i64 = hs_escape(src, sl, dst, cap - 1) 80 dst[n] = 0 as u8 81 return dst 82} 83 84// BUILD: refuse (return -1, emit nothing) if not UX-compliant; else walk the blueprint and emit each block 85// via its nx_web_builder emitter, in order, with every untrusted text field ESCAPED at the boundary (so a 86// file/typo/model blueprint cannot inject markup -- the structural HTML/CSS is the emitter's own, only field 87// VALUES are escaped). Returns the UX grade (0-100) on success. Unknown block kinds are skipped (an older 88// engine ignores a newer block kind instead of corrupting the page). 89func sg_build_page(fd: i64, title: *u8, kind: *i64, a: *i64, b: *i64, c: *i64, d: *i64, list: *i64, lcount: *i64, n: i64) -> i64 { 90 if sg_compliant(kind, b, c, lcount, n) == 0 { return 0 - 1 } 91 wb_doc_open(fd, sg_esc(title)) 92 var i: i64 = 0 93 while i < n { 94 let k: i64 = kind[i] 95 if k == SG_HEADER { wb_header(fd, sg_esc(a[i] as *u8), sg_esc(b[i] as *u8), sg_esc(c[i] as *u8)) } 96 if k == SG_HERO { wb_hero(fd, sg_esc(a[i] as *u8), sg_esc(b[i] as *u8), sg_esc(c[i] as *u8), sg_esc(d[i] as *u8)) } 97 if k == SG_SEARCH { wb_search(fd, sg_esc(a[i] as *u8), sg_esc(b[i] as *u8)) } 98 if k == SG_CARDS { 99 wb_cards_open(fd, sg_esc(a[i] as *u8)) 100 let items: *i64 = list[i] as *i64 101 var ci: i64 = 0 102 while ci < lcount[i] { 103 let bi: i64 = ci * 2 104 wb_card(fd, sg_esc(items[bi] as *u8), sg_esc(items[bi+1] as *u8)) 105 ci = ci + 1 106 } 107 wb_cards_close(fd) 108 } 109 if k == SG_STEPS { 110 let labels: *i64 = list[i] as *i64 111 let escs: *i64 = sys_mmap(8*64) as *i64 112 var si: i64 = 0 113 while si < lcount[i] { escs[si] = sg_esc(labels[si] as *u8) as i64; si = si + 1 } 114 wb_steps(fd, sg_esc(a[i] as *u8), escs, lcount[i]) 115 } 116 if k == SG_TRUST { 117 let sigs: *i64 = list[i] as *i64 118 let esct: *i64 = sys_mmap(8*64) as *i64 119 var ti: i64 = 0 120 while ti < lcount[i] { esct[ti] = sg_esc(sigs[ti] as *u8) as i64; ti = ti + 1 } 121 wb_trust(fd, esct, lcount[i]) 122 } 123 if k == SG_FOOTER { wb_footer(fd, sg_esc(a[i] as *u8)) } 124 if k == SG_FAQ { 125 wb_faq_open(fd, sg_esc(a[i] as *u8)) 126 let qa: *i64 = list[i] as *i64 127 var fi: i64 = 0 128 while fi < lcount[i] { let bi: i64 = fi*2; wb_faq_item(fd, sg_esc(qa[bi] as *u8), sg_esc(qa[bi+1] as *u8)); fi = fi + 1 } 129 wb_faq_close(fd) 130 } 131 if k == SG_GALLERY { 132 wb_gallery_open(fd, sg_esc(a[i] as *u8)) 133 let gimg: *i64 = list[i] as *i64 134 var gj: i64 = 0 135 while gj < lcount[i] { let bi: i64 = gj*2; wb_gallery_item(fd, sg_esc(gimg[bi] as *u8), sg_esc(gimg[bi+1] as *u8)); gj = gj + 1 } 136 wb_gallery_close(fd) 137 } 138 if k == SG_CTA { wb_cta(fd, sg_esc(a[i] as *u8), sg_esc(b[i] as *u8), sg_esc(c[i] as *u8), sg_esc(d[i] as *u8)) } 139 i = i + 1 140 } 141 wb_doc_close(fd) 142 return sg_grade(kind, b, c, lcount, n) 143}