code wiki / _hdl_build / nx_ims_page.nx
nx_ims_page.nx source
↩ module page · 203 lines · 9004 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_MODE_0644: i64 = 0x1a4
29const IP_LINKS_PATH: *u8 = "knowledge/registry/wiki_links.tsv"
30const IP_TYPEMAP_PREFIX: *u8 = "knowledge/store/ims-typemap-"
31const IP_OUT_DIR: *u8 = "web_assets/"
32
33// =============================================================================
34// ims_typemap.tsv reader (doc_type -> label + 3 layout flags). Mirrors the
35// sh_links_load TSV parse exactly: skip '#'/blank/header, split on TAB, "-"=>0.
36// Columns: doc_type<TAB>label<TAB>show_toc<TAB>show_infobox<TAB>show_steps (5).
37// =============================================================================
38const IP_TM_MAXROWS: i64 = 16
39const IP_TM_MAXBYTES: i64 = 8192
40const IP_TM_NCOLS: i64 = 5
41
42struct NxTypeMap {
43 raw: *u8
44 nrows: i64
45 cells: *i64 // IP_TM_MAXROWS * IP_TM_NCOLS i64s (field pointers; 0 => empty)
46 valid: i64
47}
48
49func ip_tm_field(tm: *NxTypeMap, row: i64, col: i64) -> *u8 {
50 if tm.valid != 1 { return 0 as *u8 }
51 if row < 0 { return 0 as *u8 }
52 if row >= tm.nrows { return 0 as *u8 }
53 if col < 0 { return 0 as *u8 }
54 if col >= IP_TM_NCOLS { return 0 as *u8 }
55 return tm.cells[row * IP_TM_NCOLS + col] as *u8
56}
57
58// parse the numeric flag in a cell (NUL-terminated "0"/"1"); 0 if empty/non-numeric.
59func ip_tm_flag(tm: *NxTypeMap, row: i64, col: i64) -> i64 {
60 let s: *u8 = ip_tm_field(tm, row, col)
61 if (s as i64) == 0 { return 0 }
62 if s[0] == 49 as u8 { return 1 }
63 return 0
64}
65
66func ip_typemap_load(tm: *NxTypeMap, prefix: *u8) -> i64 {
67 tm.valid = 0
68 tm.nrows = 0
69 tm.cells = sys_mmap(IP_TM_MAXROWS * IP_TM_NCOLS * 8) as *i64
70 tm.raw = 0 as *u8
71 // read the Diataxis layout rows from the NATIVE seg_store (no TSV) via nx_native_config.
72 let hh: *i64 = ncfg_open(prefix)
73 if (hh as i64) == 0 { return 0 - 1 }
74 let cnt: i64 = ncfg_count(hh, "tm\x00" as *u8)
75 let rk: *i64 = sys_mmap(8 * 8) as *i64
76 let rv: *i64 = sys_mmap(8 * 8) as *i64
77 var rows: i64 = 0
78 var i: i64 = 0
79 while i < cnt {
80 if rows < IP_TM_MAXROWS {
81 let rf: i64 = ncfg_row(hh, "tm\x00" as *u8, i, rk, rv, 8)
82 if rf > 0 {
83 tm.cells[rows * IP_TM_NCOLS + 0] = ncfg_field(rk, rv, rf, "doc_type\x00" as *u8) as i64
84 tm.cells[rows * IP_TM_NCOLS + 1] = ncfg_field(rk, rv, rf, "label\x00" as *u8) as i64
85 tm.cells[rows * IP_TM_NCOLS + 2] = ncfg_field(rk, rv, rf, "show_toc\x00" as *u8) as i64
86 tm.cells[rows * IP_TM_NCOLS + 3] = ncfg_field(rk, rv, rf, "show_infobox\x00" as *u8) as i64
87 tm.cells[rows * IP_TM_NCOLS + 4] = ncfg_field(rk, rv, rf, "show_steps\x00" as *u8) as i64
88 rows = rows + 1
89 }
90 }
91 i = i + 1
92 }
93 tm.nrows = rows
94 tm.valid = 1
95 return rows
96}
97
98// find the typemap row for a doc_type; -1 if absent.
99func ip_type_row(tm: *NxTypeMap, doc_type: *u8) -> i64 {
100 var r: i64 = 0
101 while r < tm.nrows {
102 let dt: *u8 = ip_tm_field(tm, r, 0)
103 if sh_streq(dt, doc_type) == 1 { return r }
104 r = r + 1
105 }
106 return 0 - 1
107}
108
109// =============================================================================
110// ims_page: render slug.html. doc_type drives layout via the typemap; title +
111// body_html supply the page content; corpus_path lets a gate point at a fixture
112// store (production passes the live snapshot path; 0 => default snapshot).
113// Writes web_assets/<slug>.html. Returns bytes written (>0) or a negative IP_*.
114// =============================================================================
115func ip_render(slug: *u8, doc_type: *u8, title: *u8, body_html: *u8,
116 epoch: i64, store: *NxWikiDocStore, h: *u8) -> i64 {
117 if (slug as i64) == 0 { return 0 - IP_BAD_INPUT }
118 if (title as i64) == 0 { return 0 - IP_BAD_INPUT }
119 if (body_html as i64) == 0 { return 0 - IP_BAD_INPUT }
120
121 // curated nav model (tree/path/related/tags) + the per-type layout.
122 let wl: *NxWikiLinks = sys_mmap(128) as *NxWikiLinks
123 sh_links_load(wl, IP_LINKS_PATH)
124 let tm: *NxTypeMap = sys_mmap(128) as *NxTypeMap
125 ip_typemap_load(tm, IP_TYPEMAP_PREFIX)
126
127 let trow: i64 = ip_type_row(tm, doc_type)
128 // layout flags (defaults if the doc_type is unknown: full prose page).
129 var label: *u8 = doc_type
130 var show_ib: i64 = 1
131 var show_steps: i64 = 0
132 if trow >= 0 {
133 let lb: *u8 = ip_tm_field(tm, trow, 1)
134 if (lb as i64) != 0 { label = lb }
135 show_ib = ip_tm_flag(tm, trow, 3)
136 show_steps = ip_tm_flag(tm, trow, 4)
137 }
138
139 var w: i64 = 0
140 // ---- head + nav + the WALK FURNITURE (breadcrumb+tree+prev/next+related+backlinks) ----
141 w = sh_head(h, w, title, slug)
142 w = sh_nav(h, w, slug)
143 w = sh_walk_furniture(h, w, wl, store, slug)
144
145 // ---- page H1 + freshness chip stamping the Diataxis type ----
146 w = sh_cat(h, w, "<h1>" as *u8); w = sh_cat(h, w, title); w = sh_cat(h, w, "</h1>\n" as *u8)
147 w = sh_cat(h, w, "<p class=\"fresh\">⟳ Generated LIVE by <code>nx_ims_page</code> at epoch=" as *u8)
148 w = sh_catn(h, w, epoch)
149 w = sh_cat(h, w, " — a Diataxis <strong>" as *u8); w = sh_cat(h, w, label)
150 w = sh_cat(h, w, "</strong> page (type-aware layout, reusable generator).</p>\n" as *u8)
151
152 // ---- optional infobox (type-driven) ----
153 if show_ib == 1 {
154 w = sh_infobox_open(h, w, title)
155 w = sh_infobox_row(h, w, "Type" as *u8, label)
156 w = sh_infobox_row(h, w, "Generated by" as *u8, "nx_ims_page (reusable)" as *u8)
157 w = sh_infobox_row(h, w, "Slug" as *u8, slug)
158 if show_steps == 1 {
159 w = sh_infobox_row(h, w, "Layout" as *u8, "task-oriented (steps)" as *u8)
160 } else {
161 w = sh_infobox_row(h, w, "Layout" as *u8, "knowledge-oriented (prose)" as *u8)
162 }
163 w = sh_infobox_close(h, w)
164 }
165
166 // ---- the page's own content: one anchored section wrapping the caller body ----
167 // (step pages get a task-cue heading; prose pages an overview heading -- the
168 // data-driven layout difference, kept deliberately small.)
169 if show_steps == 1 {
170 w = sh_section(h, w, "steps" as *u8, "Steps" as *u8)
171 } else {
172 w = sh_section(h, w, "overview" as *u8, "Overview" as *u8)
173 }
174 w = sh_cat(h, w, body_html)
175
176 // ---- footer (see-also + categories from wiki_links.tsv, provenance) ----
177 w = sh_footer_wl(h, w, wl, slug, epoch)
178 return w
179}
180
181// production convenience: build the live corpus store + a wall-clock epoch, render,
182// and WRITE web_assets/<slug>.html. Returns bytes written (>0) or negative IP_*.
183func ims_page(slug: *u8, doc_type: *u8, title: *u8, body_html: *u8) -> i64 {
184 let now: i64 = sys_now_realtime_sec()
185 let store: *NxWikiDocStore = sh_corpus_store()
186 let h: *u8 = sys_mmap(IP_CAP)
187 let w: i64 = ip_render(slug, doc_type, title, body_html, now, store, h)
188 if w < 0 { return w }
189
190 // build the output path web_assets + slug + .html (via sh_cat, NUL-terminated).
191 let path: *u8 = sys_mmap(256)
192 var po: i64 = 0
193 po = sh_cat(path, po, IP_OUT_DIR)
194 po = sh_cat(path, po, slug)
195 po = sh_cat(path, po, ".html" as *u8)
196 path[po] = 0 as u8
197
198 let fd: i64 = sys_openat_wr(path, IP_MODE_0644)
199 if fd < 0 { return 0 - IP_WRITE_FAIL }
200 sys_write(fd, h, w)
201 sys_close(fd)
202 return w
203}