code wiki / _hdl_build / nx_meal_skills_page.nx

nx_meal_skills_page.nx source

↩ module page · 299 lines · 16138 B

1// nx_meal_skills_page.nx -- the SKILLS surface: what a dish actually asks of you, what it would cost to skip, 2// and who you can learn each technique from. 3// 4// It is a separate emitter from nx_meal_page on purpose (rule 9). The recipe page answers "what does this cost 5// and where did it come from"; this one answers "can I do it, how long will it take ME, and should I even try 6// tonight". Bolting both into one render would have meant editing a surface that is already 9/9 gate-proven. 7// 8// It composes four existing engines and adds none: nx_meal_effort (skill tree, per-cook time, make-vs-buy), 9// nx_coach_curriculum_lib (the technique checklists -- the SAME engine that coaches NVC), nx_skill_video (who 10// you learn from), and nx_nishi_editorial (the design system). 11// 12// WHAT IT REFUSES TO SAY, and each refusal is inherited rather than re-invented: 13// - a step whose skill we have never assessed shows UNMEASURED, and is excluded from the total 14// - if an hour of this household's time is unpriced, the ledger says so instead of ranking on invented money 15// - an option missing money or time is listed and NOT ranked 16// - a video source claims a preserved copy only when its bytes come back out of the WARC 17// - the request button renders only when the intake is actually listening 18// license_tier: ORIGINAL No hw writes (Rule 26). 19import "nx_nishi_editorial.nx" 20import "nx_meal_effort.nx" 21import "nx_skill_video.nx" 22import "nx_coach_curriculum_lib.nx" 23import "nx_meal_plan.nx" 24import "nx_syscalls.nx" 25 26const MSP_FLD: i64 = 1024 27const MSP_BIG: i64 = 4096 28const MSP_MAX: i64 = 64 29 30// minutes, rendered from seconds 31func msp_mins(out: *u8, off: i64, seconds: i64) -> i64 { 32 var o: i64 = ed_num(out, off, (seconds + 30) / 60) 33 return ed_raw(out, o, " min" as *u8) 34} 35 36func msp_level_name(level: i64) -> *u8 { 37 if level == 0 { return "Never done it" as *u8 } 38 if level == 1 { return "Getting there" as *u8 } 39 if level == 2 { return "Competent" as *u8 } 40 if level == 3 { return "Proficient" as *u8 } 41 if level == 4 { return "Expert" as *u8 } 42 return "Not assessed" as *u8 43} 44 45// one step: what it asks, which skill, where that skill sits, and what it costs THIS cook 46func msp_step(out: *u8, off: i64, prefix: *u8, unit: *u8, rid: *u8, idx: i64) -> i64 { 47 let label: *u8 = sys_mmap(MSP_FLD) 48 let sid: *u8 = sys_mmap(MSP_FLD) 49 ef_step_field(prefix, rid, idx, EF_P_LABEL, label) 50 ef_step_field(prefix, rid, idx, EF_P_SKILL, sid) 51 let lv: i64 = ef_level(prefix, unit, sid) 52 let secs: i64 = ef_step_time(prefix, unit, rid, idx) 53 54 var o: i64 = ed_raw(out, off, "<div class='cell' data-state='" as *u8) 55 if secs < 0 { o = ed_raw(out, o, "alert" as *u8) } else { 56 if lv >= 3 { o = ed_raw(out, o, "have" as *u8) } else { o = ed_raw(out, o, "buy" as *u8) } 57 } 58 o = ed_raw(out, o, "'><span class='cell-tier'>Step " as *u8) 59 o = ed_num(out, o, idx + 1) 60 o = ed_raw(out, o, "</span><span class='cell-name'>" as *u8) 61 o = ed_esc(out, o, label) 62 o = ed_raw(out, o, "</span><span class='cell-state'>" as *u8) 63 o = ed_esc(out, o, msp_level_name(lv)) 64 o = ed_raw(out, o, "</span><p class='cell-note'>" as *u8) 65 66 let path: *u8 = sys_mmap(MSP_BIG) 67 if ef_skill_path(prefix, sid, path) > 0 { 68 o = ed_esc(out, o, path) 69 } else { 70 o = ed_esc(out, o, sid) 71 } 72 if secs < 0 { 73 o = ed_raw(out, o, " &middot; we have not measured you on this, so it is held out of the time total rather than guessed" as *u8) 74 } else { 75 o = ed_raw(out, o, " &middot; about " as *u8) 76 o = msp_mins(out, o, secs) 77 o = ed_raw(out, o, " at your pace" as *u8) 78 } 79 return ed_raw(out, o, "</p></div>" as *u8) 80} 81 82// the technique checklist for one skill, straight out of the shared curriculum store 83func msp_checklist(out: *u8, off: i64, h: *i64, sid: *u8, label: *u8) -> i64 { 84 let n: i64 = cc_element_count(h, sid) 85 if n <= 0 { return off } 86 var o: i64 = ed_raw(out, off, "<div class='track'><h3>" as *u8) 87 o = ed_esc(out, o, label) 88 o = ed_raw(out, o, "</h3>" as *u8) 89 let ol: *i64 = sys_mmap(16) as *i64 90 let pr: *u8 = cc_prompt(h, sid, ol) 91 if ol[0] > 0 { 92 o = ed_raw(out, o, "<p class='form-hint'>" as *u8) 93 var k: i64 = 0 94 while k < ol[0] { out[o] = pr[k]; o = o + 1; k = k + 1 } 95 o = ed_raw(out, o, "</p>" as *u8) 96 } 97 o = ed_raw(out, o, "<ol class='steps'>" as *u8) 98 var i: i64 = 0 99 while i < n { 100 let em: *u8 = cc_element(h, sid, i, ol) 101 o = ed_raw(out, o, "<li>" as *u8) 102 var k2: i64 = 0 103 while k2 < ol[0] { out[o] = em[k2]; o = o + 1; k2 = k2 + 1 } 104 let dr: *u8 = cc_drill(h, sid, i, ol) 105 if ol[0] > 0 { 106 o = ed_raw(out, o, "<br><span class='cell-note'>Drill: " as *u8) 107 var k3: i64 = 0 108 while k3 < ol[0] { out[o] = dr[k3]; o = o + 1; k3 = k3 + 1 } 109 o = ed_raw(out, o, "</span>" as *u8) 110 } 111 o = ed_raw(out, o, "</li>" as *u8) 112 i = i + 1 113 } 114 return ed_raw(out, o, "</ol></div>" as *u8) 115} 116 117// the ways to watch this technique done, and whether we still hold a copy of each 118func msp_sources(out: *u8, off: i64, vp: *u8, sid: *u8, warc: *u8, warclen: i64) -> i64 { 119 let vids: *i64 = sys_mmap(8 * MSP_MAX) as *i64 120 let n: i64 = sv_by_skill(vp, sid, vids) 121 if n <= 0 { return off } 122 var o: i64 = ed_raw(out, off, "<div class='scroller'><table><thead><tr><th>Source</th><th>Who</th><th>Kind</th><th>Length</th><th>Copy held</th></tr></thead><tbody>" as *u8) 123 let f: *u8 = sys_mmap(MSP_FLD) 124 var i: i64 = 0 125 while i < n { 126 let vid: *u8 = vids[i] as *u8 127 o = ed_raw(out, o, "<tr><td>" as *u8) 128 sv_field(vp, vid, SV_F_TITLE, f) 129 o = ed_esc(out, o, f) 130 o = ed_raw(out, o, "</td><td>" as *u8) 131 sv_field(vp, vid, SV_F_CREATOR, f) 132 o = ed_esc(out, o, f) 133 o = ed_raw(out, o, "</td><td>" as *u8) 134 sv_field(vp, vid, SV_F_KIND, f) 135 if fd_streq(f, "pro" as *u8) == 1 { o = ed_tag(out, o, "info" as *u8, "professional" as *u8) } 136 if fd_streq(f, "amateur" as *u8) == 1 { o = ed_tag(out, o, "mute" as *u8, "amateur" as *u8) } 137 if fd_streq(f, "chef" as *u8) == 1 { o = ed_tag(out, o, "have" as *u8, "a chef you follow" as *u8) } 138 o = ed_raw(out, o, "</td><td class='num'>" as *u8) 139 let secs: i64 = sv_seconds(vp, vid) 140 if secs > 0 { o = msp_mins(out, o, secs) } else { o = ed_raw(out, o, "&mdash;" as *u8) } 141 o = ed_raw(out, o, "</td><td>" as *u8) 142 if sv_is_preserved(vp, vid, warc, warclen) == 1 { 143 o = ed_raw(out, o, "<span class='credit-note kept'>" as *u8) 144 o = ed_num(out, o, sv_preserved_bytes(vp, vid, warc, warclen)) 145 o = ed_raw(out, o, " bytes preserved</span>" as *u8) 146 } else { 147 o = ed_raw(out, o, "<span class='credit-note risk'>not preserved &middot; can rot</span>" as *u8) 148 } 149 o = ed_raw(out, o, "</td></tr>" as *u8) 150 i = i + 1 151 } 152 return ed_raw(out, o, "</tbody><caption>Whose demonstration you watch is your choice. An amateur kitchen often looks more like yours than a studio does; neither is ranked above the other here.</caption></table></div>" as *u8) 153} 154 155// ---- the page ---- 156func msp_render(out: *u8, prefix: *u8, vp: *u8, currprefix: *u8, unit: *u8, rid: *u8, warc: *u8, warclen: i64, scratch_money: i64, scratch_complete: i64, intake_live: i64, back: *u8) -> i64 { 157 let title: *u8 = sys_mmap(MSP_FLD) 158 mp_field(prefix, rid, MP_F_TITLE, title) 159 let ulabel: *u8 = sys_mmap(MSP_FLD) 160 mp_unit_field(prefix, unit, MP_U_LABEL, ulabel) 161 162 let head: *u8 = sys_mmap(MSP_BIG) 163 var ho: i64 = ed_raw(head, 0, "Skills ยท " as *u8) 164 ho = ed_raw(head, ho, "" as *u8) 165 head[0] = 0 as u8 166 var o: i64 = ed_doc_open(out, 0, title) 167 168 // ---------- masthead ---------- 169 o = ed_raw(out, o, "<header class='mast'><p class='mast-eyebrow'>Skills &middot; what this dish asks of you</p><h1>" as *u8) 170 o = ed_esc(out, o, title) 171 o = ed_raw(out, o, "</h1><p class='thesis'>Every step below names the one technique it exercises, where that technique sits in the wider craft, and how long it takes at YOUR measured pace &mdash; not a stranger's.</p><div class='mast-meta'>" as *u8) 172 o = ed_raw(out, o, "<span><b>Cooking for</b> " as *u8) 173 o = ed_esc(out, o, ulabel) 174 o = ed_raw(out, o, "</span><span><b>Steps</b> " as *u8) 175 let nsteps: i64 = ef_step_count(prefix, rid) 176 o = ed_num(out, o, nsteps) 177 o = ed_raw(out, o, "</span></div></header>" as *u8) 178 179 o = ed_raw(out, o, "<ul class='filters'><li><a href='" as *u8) 180 o = ed_esc(out, o, back) 181 o = ed_raw(out, o, "'>&larr; the recipe</a></li><li><a aria-current='page' href='#'>Skills</a></li></ul>" as *u8) 182 183 // ---------- 01 the steps ---------- 184 o = ed_sec_open(out, o, "01" as *u8, "What it asks of you" as *u8) 185 o = ed_raw(out, o, "<div class='grid'>" as *u8) 186 var i: i64 = 0 187 while i < nsteps { o = msp_step(out, o, prefix, unit, rid, i); i = i + 1 } 188 o = ed_raw(out, o, "</div>" as *u8) 189 let tm: *i64 = sys_mmap(8 * 4) as *i64 190 ef_cook_time(prefix, unit, rid, tm) 191 o = ed_raw(out, o, "<p class='key'>Active time at your pace: " as *u8) 192 o = msp_mins(out, o, tm[0]) 193 if tm[1] > 0 { 194 o = ed_raw(out, o, " across " as *u8) 195 o = ed_num(out, o, tm[2] - tm[1]) 196 o = ed_raw(out, o, " of " as *u8) 197 o = ed_num(out, o, tm[2]) 198 o = ed_raw(out, o, " steps &mdash; the rest are unmeasured" as *u8) 199 } 200 o = ed_raw(out, o, "</p>" as *u8) 201 o = ed_sec_close(out, o) 202 203 // ---------- 02 cook it or buy it ---------- 204 o = ed_sec_open(out, o, "02" as *u8, "Cook it, or buy it" as *u8) 205 let opt: *i64 = sys_mmap(8 * EF_O_SLOTS * 8) as *i64 206 let nopt: i64 = ef_compare(prefix, unit, rid, scratch_money, scratch_complete, opt, 8) 207 let best: i64 = ef_best(opt, nopt) 208 o = ed_raw(out, o, "<div class='scroller'><table><thead><tr><th>Option</th><th>Money</th><th>Your time</th><th>Time cost</th><th>Total</th></tr></thead><tbody>" as *u8) 209 var k: i64 = 0 210 while k < nopt { 211 let b: i64 = k * EF_O_SLOTS 212 o = ed_raw(out, o, "<tr" as *u8) 213 if k == best { o = ed_raw(out, o, " data-hi" as *u8) } 214 o = ed_raw(out, o, "><td>" as *u8) 215 o = ed_esc(out, o, opt[b + EF_O_KIND] as *u8) 216 o = ed_raw(out, o, "</td><td class='num'>" as *u8) 217 if opt[b + EF_O_MONEY] < 0 { o = ed_raw(out, o, "unknown" as *u8) } else { o = ed_money(out, o, opt[b + EF_O_MONEY]) } 218 o = ed_raw(out, o, "</td><td class='num'>" as *u8) 219 if opt[b + EF_O_SECONDS] < 0 { o = ed_raw(out, o, "unmeasured" as *u8) } else { o = msp_mins(out, o, opt[b + EF_O_SECONDS]) } 220 o = ed_raw(out, o, "</td><td class='num'>" as *u8) 221 if opt[b + EF_O_KNOWN] == 0 { o = ed_raw(out, o, "&mdash;" as *u8) } else { o = ed_money(out, o, opt[b + EF_O_TIMECOST]) } 222 o = ed_raw(out, o, "</td><td class='num'>" as *u8) 223 if opt[b + EF_O_KNOWN] == 0 { o = ed_raw(out, o, "not ranked" as *u8) } else { o = ed_money(out, o, opt[b + EF_O_TOTAL]) } 224 o = ed_raw(out, o, "</td></tr>" as *u8) 225 k = k + 1 226 } 227 o = ed_raw(out, o, "</tbody><caption>Time is valued at this household's own stated rate. Options missing either a price or a time are shown but never ranked.</caption></table></div>" as *u8) 228 229 if ef_time_is_valued(prefix, unit) == 0 { 230 o = ed_note(out, o, "flag" as *u8, "Your time is not priced, so this ranks on money alone" as *u8, "Nobody has said what an hour of this household's time is worth, so every option above counts your evening as free -- which will always make cooking from scratch look like the winner. Set a value and the ranking may change." as *u8) 231 } 232 let unranked: i64 = ef_unranked(opt, nopt) 233 if unranked > 0 { 234 let ub: *u8 = sys_mmap(MSP_BIG) 235 var uo: i64 = ed_num(ub, 0, unranked) 236 uo = ed_raw(ub, uo, " of these options is missing either a price or a measured time, so it is listed but left out of the ranking. Being cheapest on the half we measured is not being cheapest." as *u8) 237 ub[uo] = 0 as u8 238 o = ed_note(out, o, "open" as *u8, "Not everything here can be compared" as *u8, ub) 239 } 240 if best >= 0 { 241 o = ed_raw(out, o, "<p class='kicker'>On tonight's numbers the cheapest way to eat this is <b>" as *u8) 242 o = ed_esc(out, o, opt[best * EF_O_SLOTS + EF_O_KIND] as *u8) 243 o = ed_raw(out, o, "</b> at " as *u8) 244 o = ed_money(out, o, opt[best * EF_O_SLOTS + EF_O_TOTAL]) 245 o = ed_raw(out, o, " all in. Get measured on the missing techniques and that answer can change &mdash; skill is a term in this arithmetic, not a badge.</p>" as *u8) 246 } 247 o = ed_sec_close(out, o) 248 249 // ---------- 03 learn the technique ---------- 250 o = ed_sec_open(out, o, "03" as *u8, "Learn the technique" as *u8) 251 let h: *i64 = ss_open(currprefix) 252 let seen: *i64 = sys_mmap(8 * MSP_MAX) as *i64 253 var nseen: i64 = 0 254 var s: i64 = 0 255 while s < nsteps { 256 let sid: *u8 = sys_mmap(MSP_FLD) 257 ef_step_field(prefix, rid, s, EF_P_SKILL, sid) 258 var dup: i64 = 0 259 var q: i64 = 0 260 while q < nseen { if fd_streq(seen[q] as *u8, sid) == 1 { dup = 1 } q = q + 1 } 261 if dup == 0 { 262 seen[nseen] = sid as i64 263 nseen = nseen + 1 264 let nm: *u8 = sys_mmap(MSP_FLD) 265 if ef_skill_field(prefix, sid, EF_S_NAME, nm) == 0 { nm[0] = 0 as u8 } 266 o = ed_raw(out, o, "<div class='tracks'>" as *u8) 267 if (h as i64) != 0 { o = msp_checklist(out, o, h, sid, nm) } 268 o = ed_raw(out, o, "</div>" as *u8) 269 o = msp_sources(out, o, vp, sid, warc, warclen) 270 } 271 s = s + 1 272 } 273 o = ed_sec_close(out, o) 274 275 // ---------- 04 ask for what is missing ---------- 276 o = ed_sec_open(out, o, "04" as *u8, "Ask for what is missing" as *u8) 277 if intake_live == 1 { 278 o = ed_raw(out, o, "<p class='lede'>If the cook you follow has not covered one of these techniques, ask them for it. Requests are counted by PERSON, not by click, and a chef is only ever asked for something they have not already published.</p>" as *u8) 279 o = ed_raw(out, o, "<form class='form' method='post' action='/intake/kitchen-request'>" as *u8) 280 o = ed_raw(out, o, "<label>Which technique<input name='name' maxlength='120' placeholder='dicing an onion'></label>" as *u8) 281 o = ed_raw(out, o, "<label>Whose take you want<input name='email' maxlength='160' placeholder='the cook you follow'></label>" as *u8) 282 o = ed_raw(out, o, "<label>Anything else<textarea name='message' maxlength='1000'></textarea></label>" as *u8) 283 o = ed_raw(out, o, "<input class='hp' type='text' name='website' tabindex='-1' aria-hidden='true' autocomplete='off'>" as *u8) 284 o = ed_raw(out, o, "<input type='hidden' name='back' value='" as *u8) 285 o = ed_esc(out, o, back) 286 o = ed_raw(out, o, "'><button type='submit'>Request it</button></form>" as *u8) 287 } else { 288 o = ed_note(out, o, "flag" as *u8, "Requests are not open yet" as *u8, "The intake service that would receive a request is built and gate-proven, but nothing is answering on this host right now, so there is no form here. A request box that quietly swallows what you ask for is worse than none." as *u8) 289 } 290 o = ed_sec_close(out, o) 291 292 o = ed_raw(out, o, "<footer class='colophon'>" as *u8) 293 o = ed_raw(out, o, "<p>Your pace comes from assessed skill levels, not averages. A step we have never measured you on is reported as unmeasured and held out of the total rather than guessed at.</p>" as *u8) 294 o = ed_raw(out, o, "<p>Coaching here is a self-report against a technique checklist -- the same engine that coaches conversation frameworks. Filming yourself and getting automatic feedback needs video-to-keypoints, which does not exist yet; the pose comparison behind it does.</p>" as *u8) 295 o = ed_raw(out, o, "</footer>" as *u8) 296 o = ed_doc_close(out, o) 297 out[o] = 0 as u8 298 return o 299}