code wiki / _hdl_build / nx_ims_page.nx
nx_ims_page.nx source
↩ module page · 202 lines · 8967 B
1// nx_ims_page.nx -- THE REUSABLE TYPE-AWARE PAGE GENERATOR. ONE function, ims_page,
2// renders ANY real Nishi content into a fully WALKABLE, S-class typed wiki page --
3// replacing the per-page cloned emitters (nx_wiki_nist_stem_page-style copies). It
4// reuses nx_wiki_shell VERBATIM for every piece of furniture (head, nav, the walk
5// block = breadcrumb+tree+prev/next+related+backlinks, anchored sections, infobox,
6// toc, footer) and reads the page's nav relationships from wiki_links.tsv BY SLUG,
7// so a new page becomes part of the walkable tree the moment its wiki_links.tsv row
8// exists. The Diataxis doc_type drives a small, DATA-DRIVEN layout difference read
9// from knowledge/registry/ims_typemap.tsv (a type label + which optional sections
10// show) -- no layout magic in code (CLAUDE.md #11). The caller supplies title +
11// body_html (already valid, escaped HTML for the page's own content) + the corpus
12// set used by the guarded publish step (the generator itself only WRITES the file).
13//
14// REUSE (zero furniture reinvented): nx_wiki_shell sh_head/sh_nav/sh_walk_furniture/
15// sh_section/sh_infobox_*/sh_toc_*/sh_footer_wl + sh_links_load/sh_corpus_store +
16// sh_cat/sh_catn + NxWikiLinks. nx_syscalls for I/O. license_tier: ORIGINAL
17import "nx_syscalls.nx"
18import "nx_wiki_shell.nx"
19import "nx_native_config.nx"
20
21// ===== sealed verdict surface (codes 2820-2829; distinct from WM_*/SH_*) =======
22const IP_OK: i64 = 0
23const IP_BAD_INPUT: i64 = 2820
24const IP_WRITE_FAIL: i64 = 2821
25
26// ===== named sizing constants (M7) ============================================
27const IP_CAP: i64 = 262144 // 256 KB per emitted page (>> any real page)
28const IP_LINKS_PATH: *u8 = "knowledge/registry/wiki_links.tsv"
29const IP_TYPEMAP_PREFIX: *u8 = "knowledge/store/ims-typemap-"
30const IP_OUT_DIR: *u8 = "web_assets/"
31
32// =============================================================================
33// ims_typemap.tsv reader (doc_type -> label + 3 layout flags). Mirrors the
34// sh_links_load TSV parse exactly: skip '#'/blank/header, split on TAB, "-"=>0.
35// Columns: doc_type<TAB>label<TAB>show_toc<TAB>show_infobox<TAB>show_steps (5).
36// =============================================================================
37const IP_TM_MAXROWS: i64 = 16
38const IP_TM_MAXBYTES: i64 = 8192
39const IP_TM_NCOLS: i64 = 5
40
41struct NxTypeMap {
42 raw: *u8
43 nrows: i64
44 cells: *i64 // IP_TM_MAXROWS * IP_TM_NCOLS i64s (field pointers; 0 => empty)
45 valid: i64
46}
47
48func ip_tm_field(tm: *NxTypeMap, row: i64, col: i64) -> *u8 {
49 if tm.valid != 1 { return 0 as *u8 }
50 if row < 0 { return 0 as *u8 }
51 if row >= tm.nrows { return 0 as *u8 }
52 if col < 0 { return 0 as *u8 }
53 if col >= IP_TM_NCOLS { return 0 as *u8 }
54 return tm.cells[row * IP_TM_NCOLS + col] as *u8
55}
56
57// parse the numeric flag in a cell (NUL-terminated "0"/"1"); 0 if empty/non-numeric.
58func ip_tm_flag(tm: *NxTypeMap, row: i64, col: i64) -> i64 {
59 let s: *u8 = ip_tm_field(tm, row, col)
60 if (s as i64) == 0 { return 0 }
61 if s[0] == 49 as u8 { return 1 }
62 return 0
63}
64
65func ip_typemap_load(tm: *NxTypeMap, prefix: *u8) -> i64 {
66 tm.valid = 0
67 tm.nrows = 0
68 tm.cells = sys_mmap(IP_TM_MAXROWS * IP_TM_NCOLS * 8) as *i64
69 tm.raw = 0 as *u8
70 // read the Diataxis layout rows from the NATIVE seg_store (no TSV) via nx_native_config.
71 let hh: *i64 = ncfg_open(prefix)
72 if (hh as i64) == 0 { return 0 - 1 }
73 let cnt: i64 = ncfg_count(hh, "tm\x00" as *u8)
74 let rk: *i64 = sys_mmap(8 * 8) as *i64
75 let rv: *i64 = sys_mmap(8 * 8) as *i64
76 var rows: i64 = 0
77 var i: i64 = 0
78 while i < cnt {
79 if rows < IP_TM_MAXROWS {
80 let rf: i64 = ncfg_row(hh, "tm\x00" as *u8, i, rk, rv, 8)
81 if rf > 0 {
82 tm.cells[rows * IP_TM_NCOLS + 0] = ncfg_field(rk, rv, rf, "doc_type\x00" as *u8) as i64
83 tm.cells[rows * IP_TM_NCOLS + 1] = ncfg_field(rk, rv, rf, "label\x00" as *u8) as i64
84 tm.cells[rows * IP_TM_NCOLS + 2] = ncfg_field(rk, rv, rf, "show_toc\x00" as *u8) as i64
85 tm.cells[rows * IP_TM_NCOLS + 3] = ncfg_field(rk, rv, rf, "show_infobox\x00" as *u8) as i64
86 tm.cells[rows * IP_TM_NCOLS + 4] = ncfg_field(rk, rv, rf, "show_steps\x00" as *u8) as i64
87 rows = rows + 1
88 }
89 }
90 i = i + 1
91 }
92 tm.nrows = rows
93 tm.valid = 1
94 return rows
95}
96
97// find the typemap row for a doc_type; -1 if absent.
98func ip_type_row(tm: *NxTypeMap, doc_type: *u8) -> i64 {
99 var r: i64 = 0
100 while r < tm.nrows {
101 let dt: *u8 = ip_tm_field(tm, r, 0)
102 if sh_streq(dt, doc_type) == 1 { return r }
103 r = r + 1
104 }
105 return 0 - 1
106}
107
108// =============================================================================
109// ims_page: render slug.html. doc_type drives layout via the typemap; title +
110// body_html supply the page content; corpus_path lets a gate point at a fixture
111// store (production passes the live snapshot path; 0 => default snapshot).
112// Writes web_assets/<slug>.html. Returns bytes written (>0) or a negative IP_*.
113// =============================================================================
114func ip_render(slug: *u8, doc_type: *u8, title: *u8, body_html: *u8,
115 epoch: i64, store: *NxWikiDocStore, h: *u8) -> i64 {
116 if (slug as i64) == 0 { return 0 - IP_BAD_INPUT }
117 if (title as i64) == 0 { return 0 - IP_BAD_INPUT }
118 if (body_html as i64) == 0 { return 0 - IP_BAD_INPUT }
119
120 // curated nav model (tree/path/related/tags) + the per-type layout.
121 let wl: *NxWikiLinks = sys_mmap(128) as *NxWikiLinks
122 sh_links_load(wl, IP_LINKS_PATH)
123 let tm: *NxTypeMap = sys_mmap(128) as *NxTypeMap
124 ip_typemap_load(tm, IP_TYPEMAP_PREFIX)
125
126 let trow: i64 = ip_type_row(tm, doc_type)
127 // layout flags (defaults if the doc_type is unknown: full prose page).
128 var label: *u8 = doc_type
129 var show_ib: i64 = 1
130 var show_steps: i64 = 0
131 if trow >= 0 {
132 let lb: *u8 = ip_tm_field(tm, trow, 1)
133 if (lb as i64) != 0 { label = lb }
134 show_ib = ip_tm_flag(tm, trow, 3)
135 show_steps = ip_tm_flag(tm, trow, 4)
136 }
137
138 var w: i64 = 0
139 // ---- head + nav + the WALK FURNITURE (breadcrumb+tree+prev/next+related+backlinks) ----
140 w = sh_head(h, w, title, slug)
141 w = sh_nav(h, w, slug)
142 w = sh_walk_furniture(h, w, wl, store, slug)
143
144 // ---- page H1 + freshness chip stamping the Diataxis type ----
145 w = sh_cat(h, w, "<h1>" as *u8); w = sh_cat(h, w, title); w = sh_cat(h, w, "</h1>\n" as *u8)
146 w = sh_cat(h, w, "<p class=\"fresh\">⟳ Generated LIVE by <code>nx_ims_page</code> at epoch=" as *u8)
147 w = sh_catn(h, w, epoch)
148 w = sh_cat(h, w, " — a Diataxis <strong>" as *u8); w = sh_cat(h, w, label)
149 w = sh_cat(h, w, "</strong> page (type-aware layout, reusable generator).</p>\n" as *u8)
150
151 // ---- optional infobox (type-driven) ----
152 if show_ib == 1 {
153 w = sh_infobox_open(h, w, title)
154 w = sh_infobox_row(h, w, "Type" as *u8, label)
155 w = sh_infobox_row(h, w, "Generated by" as *u8, "nx_ims_page (reusable)" as *u8)
156 w = sh_infobox_row(h, w, "Slug" as *u8, slug)
157 if show_steps == 1 {
158 w = sh_infobox_row(h, w, "Layout" as *u8, "task-oriented (steps)" as *u8)
159 } else {
160 w = sh_infobox_row(h, w, "Layout" as *u8, "knowledge-oriented (prose)" as *u8)
161 }
162 w = sh_infobox_close(h, w)
163 }
164
165 // ---- the page's own content: one anchored section wrapping the caller body ----
166 // (step pages get a task-cue heading; prose pages an overview heading -- the
167 // data-driven layout difference, kept deliberately small.)
168 if show_steps == 1 {
169 w = sh_section(h, w, "steps" as *u8, "Steps" as *u8)
170 } else {
171 w = sh_section(h, w, "overview" as *u8, "Overview" as *u8)
172 }
173 w = sh_cat(h, w, body_html)
174
175 // ---- footer (see-also + categories from wiki_links.tsv, provenance) ----
176 w = sh_footer_wl(h, w, wl, slug, epoch)
177 return w
178}
179
180// production convenience: build the live corpus store + a wall-clock epoch, render,
181// and WRITE web_assets/<slug>.html. Returns bytes written (>0) or negative IP_*.
182func ims_page(slug: *u8, doc_type: *u8, title: *u8, body_html: *u8) -> i64 {
183 let now: i64 = sys_now_realtime_sec()
184 let store: *NxWikiDocStore = sh_corpus_store()
185 let h: *u8 = sys_mmap(IP_CAP)
186 let w: i64 = ip_render(slug, doc_type, title, body_html, now, store, h)
187 if w < 0 { return w }
188
189 // build the output path web_assets + slug + .html (via sh_cat, NUL-terminated).
190 let path: *u8 = sys_mmap(256)
191 var po: i64 = 0
192 po = sh_cat(path, po, IP_OUT_DIR)
193 po = sh_cat(path, po, slug)
194 po = sh_cat(path, po, ".html" as *u8)
195 path[po] = 0 as u8
196
197 let fd: i64 = sys_openat_wr(path, MODE_0644)
198 if fd < 0 { return 0 - IP_WRITE_FAIL }
199 sys_write(fd, h, w)
200 sys_close(fd)
201 return w
202}