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}