@cyberskill/design 1.1.1 → 1.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. package/DESIGN.md +28 -5
  2. package/README.md +23 -22
  3. package/SKILL.md +7 -7
  4. package/THIRD-PARTY-NOTICES.md +35 -0
  5. package/VERSION +1 -1
  6. package/_ds_bundle.js +1495 -1733
  7. package/_ds_manifest.json +1 -1
  8. package/_esm/cs.mjs +18 -4
  9. package/_esm/react.d.ts +2 -0
  10. package/_esm/react.mjs +7 -1
  11. package/_vendor/NOTICE.md +16 -0
  12. package/_vendor/babel/README.md +7 -0
  13. package/_vendor/babel/babel.min.js +4 -0
  14. package/_vendor/react/README.md +3 -0
  15. package/_vendor/react/react-dom.production.min.js +1 -0
  16. package/_vendor/react/react.production.min.js +1 -0
  17. package/assets/aurora-hoa.webp +0 -0
  18. package/assets/aurora-kim.webp +0 -0
  19. package/assets/aurora-moc.webp +0 -0
  20. package/assets/aurora-tho.webp +0 -0
  21. package/assets/aurora-thuy.webp +0 -0
  22. package/base/a11y.css +18 -0
  23. package/base/ai.css +9 -4
  24. package/base/collections.css +18 -7
  25. package/base/components.css +16 -4
  26. package/base/controls.css +116 -66
  27. package/base/data.css +4 -4
  28. package/base/feedback.css +14 -2
  29. package/base/forms.css +10 -4
  30. package/base/glass.css +7 -1
  31. package/base/interaction.css +6 -2
  32. package/base/navigation.css +9 -3
  33. package/base/reset.css +7 -1
  34. package/base/responsive.css +38 -12
  35. package/base/shell.css +15 -3
  36. package/components/_i18n/i18n.js +41 -4
  37. package/components/_i18n/strings.js +89 -22
  38. package/components/ai/ChatMessage.d.ts +2 -0
  39. package/components/ai/ChatMessage.jsx +8 -4
  40. package/components/ai/ConfidenceMeter.d.ts +2 -0
  41. package/components/ai/ConfidenceMeter.jsx +8 -6
  42. package/components/ai/HumanReviewGate.d.ts +2 -0
  43. package/components/ai/PromptInput.d.ts +2 -0
  44. package/components/ai/TypingIndicator.d.ts +2 -0
  45. package/components/ai/ai-conversation.card.html +3 -3
  46. package/components/ai/ai-lumi.card.html +3 -3
  47. package/components/ai/ai.card.html +3 -3
  48. package/components/brand/brand.card.html +3 -3
  49. package/components/button/buttons.card.html +3 -3
  50. package/components/data/Image.jsx +18 -2
  51. package/components/data/Sortable.d.ts +5 -1
  52. package/components/data/Sortable.jsx +42 -9
  53. package/components/data/Sortable.prompt.md +4 -2
  54. package/components/data/Terminal.jsx +3 -2
  55. package/components/data/Tooltip.d.ts +2 -0
  56. package/components/data/Tooltip.jsx +6 -3
  57. package/components/data/Tree.d.ts +2 -1
  58. package/components/data/Tree.jsx +137 -16
  59. package/components/data/Tree.prompt.md +1 -1
  60. package/components/data/Watermark.jsx +11 -3
  61. package/components/data/collections.card.html +3 -3
  62. package/components/data/data.card.html +3 -3
  63. package/components/datatable/DataGrid.jsx +10 -1
  64. package/components/datatable/DataTable.d.ts +24 -1
  65. package/components/datatable/DataTable.jsx +78 -5
  66. package/components/datatable/DataTable.prompt.md +2 -1
  67. package/components/datatable/datatable.card.html +20 -16
  68. package/components/dialog/Dialog.jsx +13 -45
  69. package/components/dialog/dialog.card.html +3 -3
  70. package/components/feedback/Alert.d.ts +2 -0
  71. package/components/feedback/Alert.jsx +8 -2
  72. package/components/feedback/EmptyState.d.ts +2 -0
  73. package/components/feedback/EmptyState.jsx +6 -3
  74. package/components/feedback/ProgressBar.d.ts +2 -0
  75. package/components/feedback/ProgressBar.jsx +9 -4
  76. package/components/feedback/Toast.jsx +15 -1
  77. package/components/feedback/Toast.prompt.md +2 -1
  78. package/components/feedback/feedback-2.card.html +3 -3
  79. package/components/feedback/feedback.card.html +3 -3
  80. package/components/forms/Cascader.jsx +76 -8
  81. package/components/forms/Editor.jsx +37 -2
  82. package/components/forms/FileUpload.d.ts +2 -0
  83. package/components/forms/FileUpload.jsx +22 -8
  84. package/components/forms/Form.d.ts +3 -1
  85. package/components/forms/Form.jsx +9 -1
  86. package/components/forms/Form.prompt.md +1 -1
  87. package/components/forms/Mentions.d.ts +1 -1
  88. package/components/forms/Mentions.jsx +59 -7
  89. package/components/forms/Mentions.prompt.md +3 -1
  90. package/components/forms/Rating.d.ts +3 -2
  91. package/components/forms/Rating.jsx +34 -3
  92. package/components/forms/Rating.prompt.md +1 -1
  93. package/components/forms/Select.d.ts +2 -0
  94. package/components/forms/Select.jsx +6 -2
  95. package/components/forms/Slider.d.ts +2 -0
  96. package/components/forms/Slider.jsx +5 -2
  97. package/components/forms/Switch.d.ts +2 -0
  98. package/components/forms/Switch.jsx +15 -3
  99. package/components/forms/TimePicker.jsx +3 -1
  100. package/components/forms/Toggle.d.ts +2 -0
  101. package/components/forms/Toggle.jsx +14 -2
  102. package/components/forms/forms-2.card.html +3 -3
  103. package/components/forms/forms.card.html +3 -3
  104. package/components/icon/Icon.d.ts +1 -1
  105. package/components/icon/Icon.jsx +1 -0
  106. package/components/icon/Icon.prompt.md +1 -1
  107. package/components/icon/icon.card.html +3 -3
  108. package/components/logo/logo.card.html +3 -3
  109. package/components/navigation/CommandPalette.d.ts +2 -0
  110. package/components/navigation/CommandPalette.jsx +22 -7
  111. package/components/navigation/Menu.d.ts +2 -0
  112. package/components/navigation/Menu.jsx +60 -6
  113. package/components/navigation/Steps.d.ts +2 -0
  114. package/components/navigation/Steps.jsx +6 -3
  115. package/components/navigation/Tabs.d.ts +2 -0
  116. package/components/navigation/Tabs.jsx +6 -3
  117. package/components/navigation/Toolbar.d.ts +3 -2
  118. package/components/navigation/Toolbar.jsx +119 -12
  119. package/components/navigation/Toolbar.prompt.md +1 -1
  120. package/components/navigation/navigation.card.html +3 -3
  121. package/components/navigation/shell.card.html +3 -3
  122. package/components/overlays/AlertDialog.jsx +19 -49
  123. package/components/overlays/ContextMenu.d.ts +2 -0
  124. package/components/overlays/ContextMenu.jsx +6 -3
  125. package/components/overlays/Drawer.d.ts +2 -0
  126. package/components/overlays/Drawer.jsx +24 -20
  127. package/components/overlays/OverlayManager.d.ts +40 -0
  128. package/components/overlays/OverlayManager.jsx +214 -0
  129. package/components/overlays/OverlayManager.prompt.md +12 -0
  130. package/components/overlays/Tour.jsx +14 -7
  131. package/components/overlays/alertdialog.card.html +3 -3
  132. package/components/overlays/overlays.card.html +3 -3
  133. package/components/textfield/textfield.card.html +3 -3
  134. package/dist/styles.min.css +2252 -0
  135. package/docs/benchmark-rubric.md +1 -1
  136. package/docs/ci-cd.md +19 -12
  137. package/docs/consumer-grant.md +3 -0
  138. package/docs/consuming.md +13 -13
  139. package/docs/contrast-report.md +79 -1
  140. package/docs/conventions.md +21 -8
  141. package/docs/decisions.md +45 -7
  142. package/docs/deploy.md +17 -6
  143. package/docs/design-styles.md +2 -2
  144. package/docs/quality-gates.md +35 -15
  145. package/docs/release-notes.md +2 -2
  146. package/docs/release-runbook.md +78 -0
  147. package/docs/sync.md +2 -2
  148. package/docs/vi/benchmark-rubric.md +1 -1
  149. package/docs/vi/ci-cd.md +17 -12
  150. package/docs/vi/consumer-grant.md +3 -0
  151. package/docs/vi/consuming.md +13 -13
  152. package/docs/vi/contrast-report.md +79 -1
  153. package/docs/vi/conventions.md +20 -7
  154. package/docs/vi/decisions.md +43 -6
  155. package/docs/vi/deploy.md +17 -6
  156. package/docs/vi/quality-gates.md +34 -15
  157. package/docs/vi/release-notes.md +2 -2
  158. package/docs/vi/release-runbook.md +78 -0
  159. package/docs/vi/sync.md +2 -2
  160. package/docs/viewer.html +2 -2
  161. package/fonts/OFL.txt +98 -0
  162. package/guidelines/atomic-view.html +13 -9
  163. package/guidelines/brand-imagery.html +1 -1
  164. package/guidelines/elements-variants.html +5 -5
  165. package/guidelines/spacing-breakpoints.html +2 -1
  166. package/guidelines/surfaces-glass.html +1 -1
  167. package/llms.txt +5 -5
  168. package/package.json +38 -10
  169. package/templates/_ds-base.canonical.js +55 -0
  170. package/templates/ai-cluster-demo.html +2 -2
  171. package/templates/app-shell/AppShell.dc.html +7 -5
  172. package/templates/app-shell/content-schema.json +7 -0
  173. package/templates/app-shell/ds-base.js +33 -1
  174. package/templates/app-shell/support.js +13 -20
  175. package/templates/article/Article.dc.html +7 -3
  176. package/templates/article/content-schema.json +7 -0
  177. package/templates/article/ds-base.js +33 -1
  178. package/templates/article/support.js +13 -20
  179. package/templates/auth/Auth.dc.html +33 -12
  180. package/templates/auth/content-schema.json +37 -0
  181. package/templates/auth/ds-base.js +33 -1
  182. package/templates/auth/support.js +13 -20
  183. package/templates/bod-deck/BodDeck.dc.html +11 -10
  184. package/templates/bod-deck/ds-base.js +33 -1
  185. package/templates/bod-deck/support.js +13 -20
  186. package/templates/bod-investor-update/BodInvestorUpdate.dc.html +4 -3
  187. package/templates/bod-investor-update/ds-base.js +33 -1
  188. package/templates/bod-investor-update/support.js +13 -20
  189. package/templates/bod-memo/BodMemo.dc.html +6 -5
  190. package/templates/bod-memo/ds-base.js +33 -1
  191. package/templates/bod-memo/support.js +13 -20
  192. package/templates/bod-minutes/BodMinutes.dc.html +2 -1
  193. package/templates/bod-minutes/ds-base.js +33 -1
  194. package/templates/bod-minutes/support.js +13 -20
  195. package/templates/bod-report/BodReport.dc.html +3 -2
  196. package/templates/bod-report/ds-base.js +33 -1
  197. package/templates/bod-report/support.js +13 -20
  198. package/templates/bod-resolution/BodResolution.dc.html +5 -4
  199. package/templates/bod-resolution/ds-base.js +33 -1
  200. package/templates/bod-resolution/support.js +13 -20
  201. package/templates/culture-event-invite/CultureEventInvite.dc.html +3 -2
  202. package/templates/culture-event-invite/ds-base.js +33 -1
  203. package/templates/culture-event-invite/support.js +13 -20
  204. package/templates/dashboard/Dashboard.dc.html +10 -6
  205. package/templates/dashboard/content-schema.json +7 -0
  206. package/templates/dashboard/ds-base.js +33 -1
  207. package/templates/dashboard/support.js +13 -20
  208. package/templates/delivery-change-note/DeliveryChangeNote.dc.html +4 -3
  209. package/templates/delivery-change-note/ds-base.js +33 -1
  210. package/templates/delivery-change-note/support.js +13 -20
  211. package/templates/delivery-kickoff/DeliveryKickoff.dc.html +3 -2
  212. package/templates/delivery-kickoff/ds-base.js +33 -1
  213. package/templates/delivery-kickoff/support.js +13 -20
  214. package/templates/delivery-qbr-deck/DeliveryQbrDeck.dc.html +6 -5
  215. package/templates/delivery-qbr-deck/ds-base.js +33 -1
  216. package/templates/delivery-qbr-deck/support.js +13 -20
  217. package/templates/delivery-runbook/DeliveryRunbook.dc.html +5 -4
  218. package/templates/delivery-runbook/ds-base.js +33 -1
  219. package/templates/delivery-runbook/support.js +13 -20
  220. package/templates/delivery-status-email/DeliveryStatusEmail.dc.html +3 -2
  221. package/templates/delivery-status-email/ds-base.js +33 -1
  222. package/templates/delivery-status-email/support.js +13 -20
  223. package/templates/doc-style-guide/DocStyleGuide.dc.html +67 -64
  224. package/templates/doc-style-guide/ds-base.js +33 -1
  225. package/templates/doc-style-guide/support.js +13 -20
  226. package/templates/doc-suite-index/DocSuiteIndex.dc.html +46 -45
  227. package/templates/doc-suite-index/ds-base.js +33 -1
  228. package/templates/doc-suite-index/support.js +13 -20
  229. package/templates/doc-templates/DocTemplates.dc.html +42 -41
  230. package/templates/doc-templates/ds-base.js +33 -1
  231. package/templates/doc-templates/support.js +13 -20
  232. package/templates/email/Email.dc.html +3 -2
  233. package/templates/email/ds-base.js +33 -1
  234. package/templates/email/support.js +13 -20
  235. package/templates/email-safe/email-announcement.html +2 -2
  236. package/templates/email-safe/email-dunning.html +2 -2
  237. package/templates/email-safe/email-investor.html +2 -2
  238. package/templates/email-safe/email-launch.html +2 -2
  239. package/templates/email-safe/email-newsletter.html +2 -2
  240. package/templates/email-safe/email-status.html +2 -2
  241. package/templates/email-safe/email-transactional.html +2 -2
  242. package/templates/finance-dunning-email/FinanceDunningEmail.dc.html +6 -5
  243. package/templates/finance-dunning-email/ds-base.js +33 -1
  244. package/templates/finance-dunning-email/support.js +13 -20
  245. package/templates/finance-expense-report/FinanceExpenseReport.dc.html +4 -3
  246. package/templates/finance-expense-report/ds-base.js +33 -1
  247. package/templates/finance-expense-report/support.js +13 -20
  248. package/templates/finance-invoice/FinanceInvoice.dc.html +6 -5
  249. package/templates/finance-invoice/ds-base.js +33 -1
  250. package/templates/finance-invoice/support.js +13 -20
  251. package/templates/finance-quote/FinanceQuote.dc.html +6 -5
  252. package/templates/finance-quote/ds-base.js +33 -1
  253. package/templates/finance-quote/support.js +13 -20
  254. package/templates/hr-announcement/HrAnnouncement.dc.html +5 -4
  255. package/templates/hr-announcement/ds-base.js +33 -1
  256. package/templates/hr-announcement/support.js +13 -20
  257. package/templates/hr-interview-kit/HrInterviewKit.dc.html +5 -4
  258. package/templates/hr-interview-kit/ds-base.js +33 -1
  259. package/templates/hr-interview-kit/support.js +13 -20
  260. package/templates/hr-offboarding/HrOffboarding.dc.html +2 -1
  261. package/templates/hr-offboarding/ds-base.js +33 -1
  262. package/templates/hr-offboarding/support.js +13 -20
  263. package/templates/hr-performance-review/HrPerformanceReview.dc.html +2 -1
  264. package/templates/hr-performance-review/ds-base.js +33 -1
  265. package/templates/hr-performance-review/support.js +13 -20
  266. package/templates/hr-pip/HrPip.dc.html +2 -1
  267. package/templates/hr-pip/ds-base.js +33 -1
  268. package/templates/hr-pip/support.js +13 -20
  269. package/templates/kitchen-sink.html +9 -4
  270. package/templates/legal-msa/LegalMsa.dc.html +2 -1
  271. package/templates/legal-msa/ds-base.js +33 -1
  272. package/templates/legal-msa/support.js +13 -20
  273. package/templates/legal-sow/LegalSow.dc.html +2 -1
  274. package/templates/legal-sow/ds-base.js +33 -1
  275. package/templates/legal-sow/support.js +13 -20
  276. package/templates/marketing-campaign-brief/MarketingCampaignBrief.dc.html +3 -2
  277. package/templates/marketing-campaign-brief/ds-base.js +33 -1
  278. package/templates/marketing-campaign-brief/support.js +13 -20
  279. package/templates/marketing-case-study/MarketingCaseStudy.dc.html +6 -5
  280. package/templates/marketing-case-study/ds-base.js +33 -1
  281. package/templates/marketing-case-study/support.js +13 -20
  282. package/templates/marketing-launch/MarketingLaunch.dc.html +6 -5
  283. package/templates/marketing-launch/ds-base.js +33 -1
  284. package/templates/marketing-launch/support.js +13 -20
  285. package/templates/marketing-newsletter/MarketingNewsletter.dc.html +13 -12
  286. package/templates/marketing-newsletter/ds-base.js +33 -1
  287. package/templates/marketing-newsletter/support.js +13 -20
  288. package/templates/marketing-page/MarketingPage.dc.html +19 -14
  289. package/templates/marketing-page/content-schema.json +1 -0
  290. package/templates/marketing-page/ds-base.js +33 -1
  291. package/templates/marketing-page/support.js +13 -20
  292. package/templates/marketing-press-release/MarketingPressRelease.dc.html +2 -1
  293. package/templates/marketing-press-release/ds-base.js +33 -1
  294. package/templates/marketing-press-release/support.js +13 -20
  295. package/templates/marketing-social-kit/MarketingSocialKit.dc.html +5 -4
  296. package/templates/marketing-social-kit/ds-base.js +33 -1
  297. package/templates/marketing-social-kit/support.js +13 -20
  298. package/templates/sales-one-pager/SalesOnePager.dc.html +5 -4
  299. package/templates/sales-one-pager/ds-base.js +33 -1
  300. package/templates/sales-one-pager/support.js +13 -20
  301. package/templates/sales-proposal-deck/SalesProposalDeck.dc.html +7 -6
  302. package/templates/sales-proposal-deck/ds-base.js +33 -1
  303. package/templates/sales-proposal-deck/support.js +13 -20
  304. package/templates/settings/Settings.dc.html +7 -3
  305. package/templates/settings/content-schema.json +7 -0
  306. package/templates/settings/ds-base.js +33 -1
  307. package/templates/settings/support.js +13 -20
  308. package/templates/slide-deck/SlideDeck.dc.html +5 -4
  309. package/templates/slide-deck/ds-base.js +33 -1
  310. package/templates/slide-deck/support.js +13 -20
  311. package/templates/team-meeting-agenda/TeamMeetingAgenda.dc.html +2 -1
  312. package/templates/team-meeting-agenda/ds-base.js +33 -1
  313. package/templates/team-meeting-agenda/support.js +13 -20
  314. package/templates/tech-incident-report/TechIncidentReport.dc.html +3 -2
  315. package/templates/tech-incident-report/ds-base.js +33 -1
  316. package/templates/tech-incident-report/support.js +13 -20
  317. package/templates/tech-release-notes/TechReleaseNotes.dc.html +6 -5
  318. package/templates/tech-release-notes/ds-base.js +33 -1
  319. package/templates/tech-release-notes/support.js +13 -20
  320. package/templates/tech-retro/TechRetro.dc.html +4 -3
  321. package/templates/tech-retro/ds-base.js +33 -1
  322. package/templates/tech-retro/support.js +13 -20
  323. package/templates/tech-rfc/TechRfc.dc.html +3 -2
  324. package/templates/tech-rfc/ds-base.js +33 -1
  325. package/templates/tech-rfc/support.js +13 -20
  326. package/templates/vn-appointment-transfer/VnAppointmentTransfer.dc.html +36 -35
  327. package/templates/vn-appointment-transfer/ds-base.js +33 -1
  328. package/templates/vn-appointment-transfer/support.js +13 -20
  329. package/templates/vn-certificate-of-employment/VnCertificateOfEmployment.dc.html +27 -26
  330. package/templates/vn-certificate-of-employment/ds-base.js +33 -1
  331. package/templates/vn-certificate-of-employment/support.js +13 -20
  332. package/templates/vn-code-of-conduct/VnCodeOfConduct.dc.html +44 -43
  333. package/templates/vn-code-of-conduct/ds-base.js +33 -1
  334. package/templates/vn-code-of-conduct/support.js +13 -20
  335. package/templates/vn-compensation-regulation/VnCompensationRegulation.dc.html +53 -52
  336. package/templates/vn-compensation-regulation/ds-base.js +33 -1
  337. package/templates/vn-compensation-regulation/support.js +13 -20
  338. package/templates/vn-contract-amendment/VnContractAmendment.dc.html +41 -40
  339. package/templates/vn-contract-amendment/ds-base.js +33 -1
  340. package/templates/vn-contract-amendment/support.js +13 -20
  341. package/templates/vn-contractor-agreement/VnContractorAgreement.dc.html +50 -49
  342. package/templates/vn-contractor-agreement/ds-base.js +33 -1
  343. package/templates/vn-contractor-agreement/support.js +13 -20
  344. package/templates/vn-data-protection-policy/VnDataProtectionPolicy.dc.html +54 -53
  345. package/templates/vn-data-protection-policy/ds-base.js +33 -1
  346. package/templates/vn-data-protection-policy/support.js +13 -20
  347. package/templates/vn-disciplinary-case-file/VnDisciplinaryCaseFile.dc.html +72 -71
  348. package/templates/vn-disciplinary-case-file/ds-base.js +33 -1
  349. package/templates/vn-disciplinary-case-file/support.js +13 -20
  350. package/templates/vn-disciplinary-schedule/VnDisciplinarySchedule.dc.html +21 -20
  351. package/templates/vn-disciplinary-schedule/ds-base.js +33 -1
  352. package/templates/vn-disciplinary-schedule/support.js +13 -20
  353. package/templates/vn-employee-request/VnEmployeeRequest.dc.html +54 -53
  354. package/templates/vn-employee-request/ds-base.js +33 -1
  355. package/templates/vn-employee-request/support.js +13 -20
  356. package/templates/vn-exit-handover/VnExitHandover.dc.html +122 -121
  357. package/templates/vn-exit-handover/ds-base.js +33 -1
  358. package/templates/vn-exit-handover/support.js +13 -20
  359. package/templates/vn-foreign-employee-pack/VnForeignEmployeePack.dc.html +31 -30
  360. package/templates/vn-foreign-employee-pack/ds-base.js +33 -1
  361. package/templates/vn-foreign-employee-pack/support.js +13 -20
  362. package/templates/vn-grassroots-democracy/VnGrassrootsDemocracy.dc.html +54 -53
  363. package/templates/vn-grassroots-democracy/ds-base.js +33 -1
  364. package/templates/vn-grassroots-democracy/support.js +13 -20
  365. package/templates/vn-infosec-policy/VnInfosecPolicy.dc.html +50 -49
  366. package/templates/vn-infosec-policy/ds-base.js +33 -1
  367. package/templates/vn-infosec-policy/support.js +13 -20
  368. package/templates/vn-internal-labor-regulations/VnInternalLaborRegulations.dc.html +84 -83
  369. package/templates/vn-internal-labor-regulations/ds-base.js +33 -1
  370. package/templates/vn-internal-labor-regulations/support.js +13 -20
  371. package/templates/vn-internship-agreement/VnInternshipAgreement.dc.html +44 -43
  372. package/templates/vn-internship-agreement/ds-base.js +33 -1
  373. package/templates/vn-internship-agreement/support.js +13 -20
  374. package/templates/vn-job-description/VnJobDescription.dc.html +28 -27
  375. package/templates/vn-job-description/ds-base.js +33 -1
  376. package/templates/vn-job-description/support.js +13 -20
  377. package/templates/vn-labor-contract/VnLaborContract.dc.html +109 -108
  378. package/templates/vn-labor-contract/ds-base.js +33 -1
  379. package/templates/vn-labor-contract/support.js +13 -20
  380. package/templates/vn-labour-management-book/VnLabourManagementBook.dc.html +32 -31
  381. package/templates/vn-labour-management-book/ds-base.js +33 -1
  382. package/templates/vn-labour-management-book/support.js +13 -20
  383. package/templates/vn-mutual-nda/VnMutualNda.dc.html +45 -44
  384. package/templates/vn-mutual-nda/ds-base.js +33 -1
  385. package/templates/vn-mutual-nda/support.js +13 -20
  386. package/templates/vn-ndnca-ip/VnNdncaIp.dc.html +73 -72
  387. package/templates/vn-ndnca-ip/ds-base.js +33 -1
  388. package/templates/vn-ndnca-ip/support.js +13 -20
  389. package/templates/vn-offer-letter/VnOfferLetter.dc.html +32 -31
  390. package/templates/vn-offer-letter/ds-base.js +33 -1
  391. package/templates/vn-offer-letter/support.js +13 -20
  392. package/templates/vn-onboarding-checklist/VnOnboardingChecklist.dc.html +16 -15
  393. package/templates/vn-onboarding-checklist/ds-base.js +33 -1
  394. package/templates/vn-onboarding-checklist/support.js +13 -20
  395. package/templates/vn-pdpl-consent/VnPdplConsent.dc.html +43 -42
  396. package/templates/vn-pdpl-consent/ds-base.js +33 -1
  397. package/templates/vn-pdpl-consent/support.js +13 -20
  398. package/templates/vn-performance-evaluation-regulation/VnPerformanceEvaluationRegulation.dc.html +65 -64
  399. package/templates/vn-performance-evaluation-regulation/ds-base.js +33 -1
  400. package/templates/vn-performance-evaluation-regulation/support.js +13 -20
  401. package/templates/vn-performance-review-form/VnPerformanceReviewForm.dc.html +49 -48
  402. package/templates/vn-performance-review-form/ds-base.js +33 -1
  403. package/templates/vn-performance-review-form/support.js +13 -20
  404. package/templates/vn-phantom-stock/VnPhantomStock.dc.html +41 -40
  405. package/templates/vn-phantom-stock/ds-base.js +33 -1
  406. package/templates/vn-phantom-stock/support.js +13 -20
  407. package/templates/vn-probation-contract/VnProbationContract.dc.html +56 -55
  408. package/templates/vn-probation-contract/ds-base.js +33 -1
  409. package/templates/vn-probation-contract/support.js +13 -20
  410. package/templates/vn-receipt-acknowledgement/VnReceiptAcknowledgement.dc.html +28 -27
  411. package/templates/vn-receipt-acknowledgement/ds-base.js +33 -1
  412. package/templates/vn-receipt-acknowledgement/support.js +13 -20
  413. package/templates/vn-remote-work-policy/VnRemoteWorkPolicy.dc.html +44 -43
  414. package/templates/vn-remote-work-policy/ds-base.js +33 -1
  415. package/templates/vn-remote-work-policy/support.js +13 -20
  416. package/templates/vn-resignation-letter/VnResignationLetter.dc.html +20 -19
  417. package/templates/vn-resignation-letter/ds-base.js +33 -1
  418. package/templates/vn-resignation-letter/support.js +13 -20
  419. package/templates/vn-salary-scale/VnSalaryScale.dc.html +39 -38
  420. package/templates/vn-salary-scale/ds-base.js +33 -1
  421. package/templates/vn-salary-scale/support.js +13 -20
  422. package/templates/vn-sow-appendix/VnSowAppendix.dc.html +21 -20
  423. package/templates/vn-sow-appendix/ds-base.js +33 -1
  424. package/templates/vn-sow-appendix/support.js +13 -20
  425. package/templates/vn-termination-decision/VnTerminationDecision.dc.html +25 -24
  426. package/templates/vn-termination-decision/ds-base.js +33 -1
  427. package/templates/vn-termination-decision/support.js +13 -20
  428. package/templates/vn-total-rewards-appendix/VnTotalRewardsAppendix.dc.html +63 -62
  429. package/templates/vn-total-rewards-appendix/ds-base.js +33 -1
  430. package/templates/vn-total-rewards-appendix/support.js +13 -20
  431. package/templates/vn-training-commitment/VnTrainingCommitment.dc.html +39 -38
  432. package/templates/vn-training-commitment/ds-base.js +33 -1
  433. package/templates/vn-training-commitment/support.js +13 -20
  434. package/templates/vn-travel-expense-policy/VnTravelExpensePolicy.dc.html +50 -49
  435. package/templates/vn-travel-expense-policy/ds-base.js +33 -1
  436. package/templates/vn-travel-expense-policy/support.js +13 -20
  437. package/tokens/colors.css +19 -1
  438. package/tokens/component-tokens.css +1 -0
  439. package/tokens/element-seeds.json +2 -2
  440. package/tokens/elements.css +228 -21
  441. package/tokens/elevation.css +8 -2
  442. package/tokens/native/CSTokens.kt +37 -2
  443. package/tokens/native/CSTokens.swift +37 -2
  444. package/tokens/native/cs_tokens.dart +37 -2
  445. package/tokens/provenance.json +12 -12
  446. package/tokens/spacing.css +4 -1
  447. package/tokens/tokens.dtcg.json +129 -16
  448. package/tokens/tokens.js +59 -18
  449. package/tokens/tokens.json +58 -17
  450. package/tokens/typography.css +2 -0
  451. package/ui_kits/deck/index.html +4 -0
  452. package/ui_kits/status-hub/StatusHub.jsx +30 -4
  453. package/ui_kits/status-hub/index.html +3 -3
  454. package/ui_kits/status-hub/login.html +55 -9
  455. package/ui_kits/status-hub/project.html +4 -0
  456. package/ui_kits/status-hub/settings.html +35 -13
  457. package/ui_kits/status-hub/status.css +5 -1
  458. package/ui_kits/website/Website.jsx +25 -3
  459. package/ui_kits/website/careers.html +5 -1
  460. package/ui_kits/website/chat.html +5 -3
  461. package/ui_kits/website/index.html +3 -3
  462. package/ui_kits/website/site.css +3 -3
  463. package/ui_kits/website/work.html +5 -1
  464. package/assets/aurora-hoa.png +0 -0
  465. package/assets/aurora-kim.png +0 -0
  466. package/assets/aurora-moc.png +0 -0
  467. package/assets/aurora-thuy.png +0 -0
  468. package/docs/status/assets/favicon.svg +0 -5
  469. package/docs/status/assets/status-legacy.css +0 -399
  470. package/docs/status/assets/status-legacy.js +0 -505
  471. package/docs/status/assets/status.css +0 -644
  472. package/docs/status/assets/status.js +0 -1308
  473. package/docs/status/data/status-feed.json +0 -1
  474. package/docs/status/data/task/TASK-REL-001.js +0 -1
  475. package/docs/status/data/task/TASK-REL-002.js +0 -1
  476. package/docs/status/index.html +0 -42
  477. package/docs/status/roadmap.html +0 -4
  478. package/docs/status/status-legacy.html +0 -117
  479. package/docs/tasks/BACKLOG.md +0 -26
  480. package/docs/tasks/release/TASK-REL-001-launch-1-1-0/spec.md +0 -55
  481. package/docs/tasks/release/TASK-REL-002-republish-1-1-1/spec.md +0 -54
@@ -1,1308 +0,0 @@
1
- /* status-app.js - client for status-hub@3 (one tabless canvas).
2
- Reads status-feed@1 from #sv3-data. Zero runtime deps; works over file://.
3
- Selection model: one {kind, id} drives cross-highlighting in every band.
4
- Spec chunks load lazily from D.specDir (window.CS_SPEC) when task.sp is set. */
5
- (function () {
6
- "use strict";
7
-
8
- /* ═══ 0 · data + indexes ═══════════════════════════════════════════════ */
9
- const D = JSON.parse(document.getElementById("sv3-data").textContent);
10
- const TASKS = D.tasks || [];
11
- const BY = {}; TASKS.forEach(t => { BY[t.i] = t; });
12
- const REV = {}; // dependents: dep id -> [task ids that depend on it]
13
- TASKS.forEach(t => (t.d || []).forEach(d => (REV[d] = REV[d] || []).push(t.i)));
14
- const MODS = D.modules || [];
15
- const MOD = {}; MODS.forEach(m => { MOD[m.id] = m; });
16
- const RELS = D.releases || [];
17
- const RELBYV = {}; RELS.forEach(r => { RELBYV[r.v] = r; });
18
- const TODAY = D.snapshot;
19
-
20
- const esc = s => String(s == null ? "" : s)
21
- .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
22
- const pct = (n, d) => d ? Math.round(100 * n / d) : 0;
23
- const num = n => Number(n || 0).toLocaleString("en-US");
24
- const short = id => id.replace(/^TASK-/, "");
25
- const stLabel = s => String(s || "").replace(/_/g, " ");
26
- const dayMs = 864e5;
27
- const dnum = s => { const t = Date.parse(s); return isNaN(t) ? null : t; };
28
- const daysBetween = (a, b) => Math.round((dnum(b) - dnum(a)) / dayMs);
29
-
30
- /* ═══ 1 · derived truth ════════════════════════════════════════════════ */
31
- const unfinished = t => t.b !== "done";
32
- const unmetDeps = t => (t.d || []).filter(d => BY[d] && BY[d].b !== "done");
33
- const BLOCKED = TASKS.filter(t => unfinished(t) && unmetDeps(t).length);
34
- const BLOCKEDSET = new Set(BLOCKED.map(t => t.i));
35
- const FRONTIER = TASKS.filter(t => (t.b === "draft" || t.b === "active") && !unmetDeps(t).length);
36
- const FRONTSET = new Set(FRONTIER.map(t => t.i));
37
- const sDone = TASKS.filter(t => t.s === "done").length;
38
- const retired = TASKS.filter(t => t.b === "done" && t.s !== "done").length;
39
- const nActive = TASKS.filter(t => t.b === "active").length;
40
- const nHold = TASKS.filter(t => t.b === "hold").length;
41
- const nDraft = TASKS.filter(t => t.b === "draft").length;
42
- const STALE = TASKS.filter(t => t.b === "draft" && t.cr && daysBetween(t.cr, TODAY) > 45);
43
-
44
- /* critical path: longest chain of unfinished work along dep -> dependent edges */
45
- const wOf = t => (t.e && t.e > 0 ? t.e : 4);
46
- const cpMemo = {}, cpState = {};
47
- function cpLongest(id) {
48
- if (cpMemo[id]) return cpMemo[id];
49
- const t = BY[id];
50
- if (!t || !unfinished(t)) return { w: 0, next: null };
51
- if (cpState[id] === 1) return { w: 0, next: null }; // cycle guard
52
- cpState[id] = 1;
53
- let best = { w: 0, next: null };
54
- for (const c of (REV[id] || [])) {
55
- if (!BY[c] || !unfinished(BY[c])) continue;
56
- const r = cpLongest(c);
57
- if (r.w + wOf(BY[c]) > best.w) best = { w: r.w + wOf(BY[c]), next: c };
58
- }
59
- cpState[id] = 2;
60
- return (cpMemo[id] = { w: best.w + wOf(t), next: best.next });
61
- }
62
- let cpStart = null, cpBest = 0;
63
- for (const t of TASKS) {
64
- if (!unfinished(t)) continue;
65
- const r = cpLongest(t.i);
66
- if (r.w > cpBest) { cpBest = r.w; cpStart = t.i; }
67
- }
68
- const CRIT = [];
69
- for (let id = cpStart; id; id = cpMemo[id] && cpMemo[id].next) {
70
- CRIT.push(id); if (CRIT.length > 60) break;
71
- }
72
- const CRITSET = new Set(CRIT);
73
- const CRITNEXT = {}; CRIT.forEach((id, i) => { if (CRIT[i + 1]) CRITNEXT[id] = CRIT[i + 1]; });
74
- const cpHours = Math.round(CRIT.reduce((s, id) => s + wOf(BY[id]), 0));
75
-
76
- /* traceability: task -> releases / commits that cite it */
77
- const RELOF = {}, COMOF = {};
78
- function citeRel(v, id) { (RELOF[id] = RELOF[id] || []).push(v); }
79
- RELS.forEach(r => {
80
- const seen = new Set();
81
- ["features", "fixes", "improvements"].forEach(k => (r.notes[k] || []).forEach(it =>
82
- (it.ids || []).forEach(id => { if (!seen.has(id)) { seen.add(id); citeRel(r.v, id); } })));
83
- (r.cov.commits || []).forEach(c => (c.ids || []).forEach(id => {
84
- (COMOF[id] = COMOF[id] || []).push(c);
85
- if (!seen.has(id)) { seen.add(id); citeRel(r.v, id); }
86
- }));
87
- });
88
- (function citeUnreleased() {
89
- const un = D.unreleased || {};
90
- const seen = new Set();
91
- ["features", "fixes", "improvements"].forEach(k =>
92
- (((un.notes || {})[k]) || []).forEach(it => (it.ids || []).forEach(id => {
93
- if (!seen.has(id)) { seen.add(id); citeRel("unreleased", id); }
94
- })));
95
- ((un.cov || {}).commits || []).forEach(c => (c.ids || []).forEach(id => {
96
- (COMOF[id] = COMOF[id] || []).push(c);
97
- if (!seen.has(id)) { seen.add(id); citeRel("unreleased", id); }
98
- }));
99
- })();
100
-
101
- const sumCov = list => list.reduce((a, r) => ({
102
- linked: a.linked + (r.cov.linked || 0), exempt: a.exempt + (r.cov.exempt || 0),
103
- unlinked: a.unlinked + (r.cov.unlinked || 0),
104
- }), { linked: 0, exempt: 0, unlinked: 0 });
105
- const LEGACY = RELS.filter(r => r.lg);
106
- const CURRELS = RELS.filter(r => !r.lg);
107
- const covCur = sumCov(CURRELS.concat([{ cov: (D.unreleased || {}).cov || {} }]));
108
- const covLeg = sumCov(LEGACY);
109
- const covPct = pct(covCur.linked, covCur.linked + covCur.unlinked);
110
- const relLabel = v => v === "unreleased" ? "next" : v === "first-epoch" ? "epoch 1" : "v" + v;
111
-
112
- /* GitHub deep links (derived from origin at build time) */
113
- const GH = D.repoUrl || "";
114
- const TAGSET = new Set(D.tags || []);
115
- const ghc = h => GH ? GH + "/commit/" + h : null;
116
- const relHref = r => !GH ? null
117
- : TAGSET.has("v" + r.v) ? GH + "/releases/tag/v" + r.v
118
- : r.rc ? ghc(r.rc) : null;
119
- const extA = (href, cls, inner, title) => href
120
- ? '<a class="' + cls + '" target="_blank" rel="noopener" href="' + esc(href) + '"' +
121
- (title ? ' title="' + esc(title) + '"' : "") + ">" + inner + "</a>"
122
- : '<span class="' + cls + '">' + inner + "</span>";
123
- const hasNotes = r => r.notes && (r.notes.features.length + r.notes.fixes.length + r.notes.improvements.length) > 0;
124
- const staleDays = Math.floor((Date.now() - (dnum(TODAY) || Date.now())) / dayMs);
125
-
126
- /* per-module blocked counts */
127
- const modBlocked = {};
128
- BLOCKED.forEach(t => { modBlocked[t.m] = (modBlocked[t.m] || 0) + 1; });
129
-
130
- /* ═══ 2 · state ════════════════════════════════════════════════════════ */
131
- const S = { sel: null, flow: "focus", flowDone: true, lastSetHash: null };
132
-
133
- /* ═══ 3 · skeleton ═════════════════════════════════════════════════════ */
134
- const app = document.getElementById("app");
135
- const ICONS = {
136
- search: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6"><circle cx="7" cy="7" r="4.6"/><path d="m10.6 10.6 3 3"/></svg>',
137
- sun: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="3.2"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2M3 3l1.4 1.4M11.6 11.6 13 13M13 3l-1.4 1.4M4.4 11.6 3 13"/></svg>',
138
- moon: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M13.5 9.5A6 6 0 1 1 6.5 2.5a5 5 0 0 0 7 7z"/></svg>',
139
- warn: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M8 2 1.5 13.5h13L8 2z"/><path d="M8 6.5v3.2M8 11.8v.2"/></svg>',
140
- ok: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8"><path d="m2.5 8.5 3.5 3.5 7.5-8"/></svg>',
141
- bolt: '<svg viewBox="0 0 16 16" fill="currentColor"><path d="M9 1 3 9h4l-1 6 6-8H8l1-6z"/></svg>',
142
- link: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6.5 9.5 9.5 6.5M5 11l-1.2 1.2a2.5 2.5 0 0 1-3.5-3.5L3.5 5.5a2.5 2.5 0 0 1 3.5 0M11 5l1.2-1.2a2.5 2.5 0 0 1 3.5 3.5L12.5 10.5a2.5 2.5 0 0 1-3.5 0" transform="translate(-1.5 -0.5)"/></svg>',
143
- ship: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 11h12l-1.5 3h-9L2 11zM8 2v6M5 5l3-3 3 3"/></svg>',
144
- open: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 3H3v10h10v-3M9 2h5v5M14 2 7.5 8.5"/></svg>',
145
- graph: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="3.5" cy="8" r="1.8"/><circle cx="12.5" cy="3.5" r="1.8"/><circle cx="12.5" cy="12.5" r="1.8"/><path d="M5.2 7.2 10.8 4.2M5.2 8.8l5.6 3"/></svg>',
146
- x: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M3.5 3.5l9 9M12.5 3.5l-9 9"/></svg>',
147
- };
148
-
149
- const initial = esc((D.project || "C").charAt(0).toUpperCase());
150
- app.innerHTML =
151
- '<div class="bar"><div class="bar-in">' +
152
- '<div class="logo" aria-hidden="true">' + initial + '</div>' +
153
- '<div class="ttl">' + esc(D.project) + ' · Status<small>one page, no tabs</small></div>' +
154
- extA(GH ? GH + "/releases" : null, "vpill", "v" + esc(D.version)) +
155
- (D.branch ? extA(GH ? GH + "/tree/" + encodeURIComponent(D.branch) : null, "vpill branch",
156
- "⎇ " + esc(D.branch), "snapshot branch on GitHub") : "") +
157
- '<span class="stamp">' + extA(ghc(D.head), "stamp-a", "snapshot " + esc(TODAY) +
158
- (D.head ? " @ " + esc(D.head) : "") +
159
- (D.commit ? " · " + esc(D.commit) : "")) + "</span>" +
160
- ((D.coverageAsOf || D.head)
161
- ? '<span class="vpill" title="Commit coverage tip at render time. A commit that stages this page cannot include its own hash — CI/deploy closes the gap.">coverage as of parent ' +
162
- esc(D.coverageAsOf || D.head) + "</span>"
163
- : "") +
164
- (staleDays > 1 ? '<span class="vpill stale" title="Snapshot is more than a day old — regenerate via status-page / pre-commit">snapshot ' +
165
- staleDays + "d old</span>" : "") +
166
- (D.noGit ? '<span class="vpill stale" title="Git history was unavailable at render time">no git history available</span>' : "") +
167
- '<div class="bar-spring"></div>' +
168
- '<div id="selslot"></div>' +
169
- '<div class="search"><span class="sicon">' + ICONS.search + '</span>' +
170
- '<input id="q" type="text" placeholder="Find task, module, release…" autocomplete="off" spellcheck="false">' +
171
- '<kbd>/</kbd><div class="sr-drop" id="srdrop" hidden></div></div>' +
172
- '<button class="theme-btn" data-act="theme" title="Toggle theme" aria-label="Toggle theme">' + ICONS.moon + '</button>' +
173
- '</div></div>' +
174
- '<div class="wrap">' +
175
- '<section class="band" id="pulse"><div class="band-hd"><span class="num">01</span><h2>Pulse</h2>' +
176
- '<span class="sub">where the project stands, zero clicks</span></div>' +
177
- '<div class="hero" id="hero"></div><div class="att-row" id="attrow"></div></section>' +
178
- '<section class="band" id="roadmap"><div class="band-hd"><span class="num">02</span><h2>Roadmap</h2>' +
179
- '<span class="sub">program lanes, releases on the real calendar, shipped burn-up</span></div>' +
180
- '<div class="card road-card"><div class="road-scroll" id="roadwrap"></div>' +
181
- '<div class="road-foot"><div class="legend">' +
182
- '<span><i style="background:var(--done)"></i>shipped burn-up</span>' +
183
- '<span><i style="background:var(--accent)"></i>release</span>' +
184
- '<span><i style="background:var(--bad)"></i>release with unlinked commits</span>' +
185
- '<span><i style="background:var(--panel-3);border:1px solid var(--line)"></i>lane span · fill = done</span></div>' +
186
- '<div class="rel-strip" id="relstrip"></div></div></div></section>' +
187
- '<section class="band" id="sysmap"><div class="band-hd"><span class="num">03</span><h2>System map</h2>' +
188
- '<span class="sub">every module, sized by scope, wired by real task dependencies</span></div>' +
189
- '<div class="map-grid"><div class="card map-card" id="mapcard"></div><div class="rank" id="rank"></div></div></section>' +
190
- '<section class="band" id="flowband"><div class="band-hd"><span class="num">04</span><h2>Flow</h2>' +
191
- '<span class="sub">the dependency graph — what is ready, what is blocked, what the longest chain is</span>' +
192
- '<div class="tools" id="flowtools"></div></div>' +
193
- '<div class="card flow-card"><div class="flow-scroll" id="flowwrap"></div><div class="flow-foot" id="flowfoot"></div></div></section>' +
194
- '<section class="band" id="ledger"><div class="band-hd"><span class="num">05</span><h2>Releases &amp; traceability</h2>' +
195
- '<span class="sub">release notes in three types, every commit accounted for</span></div>' +
196
- '<div id="ledgerwrap"></div></section>' +
197
- '<section class="band" id="indexband"><div class="band-hd"><span class="num">06</span><h2>Index</h2>' +
198
- '<span class="sub">every task, grouped by module — click any row for the full record</span>' +
199
- '<div class="tools" id="idxtools"></div></div>' +
200
- '<div class="card idx-card" id="idxcard"></div></section>' +
201
- '<div class="foot">' +
202
- '<span>Generated from task frontmatter + CHANGELOG.md + git history.</span>' +
203
- '<span class="mono">' + esc(D.commit || D.fp || "") + '</span>' +
204
- '<span>Feed <span class="mono">' + esc(D.fp || "") + '</span></span>' +
205
- '<span><a href="status-legacy.html">Legacy status page (v2 lenses)</a></span>' +
206
- '</div>' +
207
- '</div>' +
208
- '<div class="scrim" id="scrim" data-act="close-drawer"></div>' +
209
- '<aside class="drawer" id="drawer" aria-label="Task detail"></aside>' +
210
- '<div id="tip" role="tooltip"></div>';
211
-
212
- /* ═══ 4 · tooltip ══════════════════════════════════════════════════════ */
213
- const tip = document.getElementById("tip");
214
- function tipShow(html, x, y) {
215
- tip.innerHTML = html;
216
- tip.classList.add("on");
217
- const w = tip.offsetWidth, h = tip.offsetHeight;
218
- let tx = x + 14, ty = y + 14;
219
- if (tx + w > innerWidth - 8) tx = x - w - 12;
220
- if (ty + h > innerHeight - 8) ty = y - h - 12;
221
- tip.style.left = Math.max(6, tx) + "px";
222
- tip.style.top = Math.max(6, ty) + "px";
223
- }
224
- const tipHide = () => tip.classList.remove("on");
225
-
226
- /* ═══ 5 · pulse ════════════════════════════════════════════════════════ */
227
- function ringSvg(p) {
228
- const r = 20, c = 2 * Math.PI * r;
229
- return '<svg class="ring" viewBox="0 0 52 52">' +
230
- '<circle cx="26" cy="26" r="' + r + '" fill="none" stroke="var(--panel-3)" stroke-width="6"/>' +
231
- '<circle cx="26" cy="26" r="' + r + '" fill="none" stroke="var(--done)" stroke-width="6" stroke-linecap="round" ' +
232
- 'stroke-dasharray="' + (c * p / 100).toFixed(1) + " " + c.toFixed(1) + '" transform="rotate(-90 26 26)"/>' +
233
- '<text x="26" y="30" text-anchor="middle">' + p + "</text></svg>";
234
- }
235
- function sparkSvg() {
236
- const B = D.burnup || [];
237
- if (B.length < 2) return "";
238
- const t0 = dnum(B[0].d), t1 = dnum(B[B.length - 1].d) || 1;
239
- const max = B[B.length - 1].n || 1;
240
- const px = d => (84 * (dnum(d) - t0) / Math.max(1, t1 - t0)).toFixed(1);
241
- const py = n => (24 - 22 * n / max).toFixed(1);
242
- let path = "M" + px(B[0].d) + " " + py(B[0].n);
243
- B.forEach(b => { path += " L" + px(b.d) + " " + py(b.n); });
244
- return '<svg class="trend" viewBox="0 0 84 26"><path d="' + path +
245
- '" fill="none" stroke="var(--done)" stroke-width="1.6"/></svg>';
246
- }
247
- const nextNotes = (D.unreleased || {}).notes || { features: [], fixes: [], improvements: [] };
248
- const nextCount = nextNotes.features.length + nextNotes.fixes.length + nextNotes.improvements.length;
249
- const unrelCov = ((D.unreleased || {}).cov || { linked: 0, exempt: 0, unlinked: 0 });
250
-
251
- document.getElementById("hero").innerHTML =
252
- '<button class="vt card tone-done" data-act="go" data-id="indexband">' +
253
- '<span class="lbl">Shipped</span>' + ringSvg(pct(sDone, TASKS.length)) +
254
- '<div class="big">' + sDone + '<small> / ' + TASKS.length + '</small></div>' +
255
- '<div class="sub"><b>' + retired + '</b> retired (closed · dup)</div>' + sparkSvg() + '</button>' +
256
- '<button class="vt card tone-active" data-act="go-flow" data-id="focus">' +
257
- '<span class="lbl">In flight</span><div class="big">' + nActive + '</div>' +
258
- '<div class="sub"><b>' + nDraft + '</b> drafts queued · <b>' + nHold + '</b> on hold</div></button>' +
259
- '<button class="vt card tone-done" data-act="go-flow" data-id="focus">' +
260
- '<span class="lbl">Ready to start</span><div class="big">' + FRONTIER.length + '</div>' +
261
- '<div class="sub">every dependency met</div></button>' +
262
- '<button class="vt card tone-bad" data-act="go-flow" data-id="focus">' +
263
- '<span class="lbl">Blocked</span><div class="big">' + BLOCKED.length + '</div>' +
264
- '<div class="sub">waiting on unfinished deps</div></button>' +
265
- '<button class="vt card ' + (covCur.unlinked ? "tone-bad" : "tone-done") + '" data-act="go" data-id="ledger">' +
266
- '<span class="lbl">Traceability</span><div class="big">' + covPct + '<small>%</small></div>' +
267
- '<div class="sub"><b>' + num(covCur.unlinked) + '</b> unlinked commits since the version reset</div></button>' +
268
- '<button class="vt card tone-hold" data-act="go" data-id="ledger">' +
269
- '<span class="lbl">Next release</span><div class="big">' + nextCount + '</div>' +
270
- '<div class="sub">notes staged · <b>' + (unrelCov.commits ? unrelCov.commits.length : 0) + '</b> commits pending</div></button>';
271
-
272
- /* attention cards */
273
- const topBlocker = (() => {
274
- const cnt = {};
275
- BLOCKED.forEach(t => unmetDeps(t).forEach(d => { cnt[d] = (cnt[d] || 0) + 1; }));
276
- let best = null; Object.keys(cnt).forEach(k => { if (!best || cnt[k] > cnt[best]) best = k; });
277
- return best ? { id: best, n: cnt[best] } : null;
278
- })();
279
- const att = [];
280
- if (covCur.unlinked)
281
- att.push({ sev: "crit", ic: ICONS.link, b: num(covCur.unlinked) + " commits break the traceability rule",
282
- s: "No task link in subject or body since the v0.1.0 reset" +
283
- (covLeg.unlinked ? " (+" + num(covLeg.unlinked) + " in the first epoch)" : "") +
284
- ". The proposed commit-msg gate + CI check makes this structurally impossible.",
285
- act: "go", id: "ledger" });
286
- if (topBlocker)
287
- att.push({ sev: "crit", ic: ICONS.warn, b: short(topBlocker.id) + " is the biggest blocker",
288
- s: "Finishing it unblocks " + topBlocker.n + " task" + (topBlocker.n > 1 ? "s" : "") + ". " + BLOCKED.length + " tasks are blocked in total.",
289
- act: "task", id: topBlocker.id });
290
- if (CRIT.length > 1)
291
- att.push({ sev: "warn", ic: ICONS.bolt, b: "Critical path: " + CRIT.length + " tasks · ~" + cpHours + "h",
292
- s: short(CRIT[0]) + " → … → " + short(CRIT[CRIT.length - 1]) + ". Longest unfinished dependency chain.",
293
- act: "go-flow", id: "focus" });
294
- if (STALE.length)
295
- att.push({ sev: "warn", ic: ICONS.warn, b: STALE.length + " drafts stale for 45+ days",
296
- s: "Oldest from " + STALE.reduce((a, t) => a < t.cr ? a : t.cr, TODAY) + ". Groom, close, or schedule.",
297
- act: "go", id: "indexband" });
298
- if (nextCount)
299
- att.push({ sev: "info", ic: ICONS.ship, b: "Next release carries " + nextCount + " staged notes",
300
- s: nextNotes.features.length + " features · " + nextNotes.fixes.length + " fixes · " + nextNotes.improvements.length + " improvements in Unreleased.",
301
- act: "go", id: "ledger" });
302
- att.push({ sev: "ok", ic: ICONS.ok, b: FRONTIER.length + " tasks are ready to start now",
303
- s: "All dependencies met — the frontier is wide open.", act: "go-flow", id: "focus" });
304
- document.getElementById("attrow").innerHTML = att.slice(0, 6).map(a =>
305
- '<button class="att ' + a.sev + '" data-act="' + a.act + '" data-id="' + esc(a.id) + '">' +
306
- '<span class="ic">' + a.ic + '</span><span><b>' + esc(a.b) + '</b><span>' + esc(a.s) + '</span></span></button>').join("");
307
-
308
- /* ═══ 6 · roadmap ══════════════════════════════════════════════════════ */
309
- (function roadmap() {
310
- const phases = D.phases || [];
311
- const W = 1180, LX = 96, RX = W - 26;
312
- const dates = [];
313
- TASKS.forEach(t => { if (t.cr) dates.push(t.cr); });
314
- RELS.forEach(r => { if (r.d) dates.push(r.d); });
315
- dates.push(TODAY);
316
- const t0 = Math.min.apply(null, dates.map(dnum).filter(Boolean)) - 3 * dayMs;
317
- const t1 = Math.max.apply(null, dates.map(dnum).filter(Boolean)) + 9 * dayMs;
318
- const X = d => LX + (RX - LX) * ((typeof d === "number" ? d : dnum(d)) - t0) / (t1 - t0);
319
-
320
- const BURN_H = 66, BURN_Y = 20;
321
- const laneH = 24, laneGap = 7;
322
- const lanesY = BURN_Y + BURN_H + 24;
323
- const axisY = lanesY + phases.length * (laneH + laneGap) + 26;
324
- const H = axisY + 44;
325
-
326
- let s = '<svg id="roadsvg" viewBox="0 0 ' + W + " " + H + '" role="img" aria-label="Roadmap">';
327
-
328
- /* month grid */
329
- const d0 = new Date(t0); d0.setDate(1);
330
- for (let d = new Date(d0); d.getTime() < t1; d.setMonth(d.getMonth() + 1)) {
331
- const x = X(d.getTime());
332
- if (x < LX - 1) continue;
333
- s += '<line class="month-line" x1="' + x.toFixed(1) + '" y1="14" x2="' + x.toFixed(1) + '" y2="' + (axisY + 6) + '"/>' +
334
- '<text class="month-lab" x="' + (x + 4).toFixed(1) + '" y="12">' +
335
- d.toLocaleString("en", { month: "short" }) + " " + String(d.getFullYear()).slice(2) + "</text>";
336
- }
337
-
338
- /* burn-up */
339
- const B = D.burnup || [];
340
- if (B.length) {
341
- const maxN = B[B.length - 1].n;
342
- const Y = n => BURN_Y + BURN_H - BURN_H * n / Math.max(1, maxN);
343
- let line = "M" + X(B[0].d).toFixed(1) + " " + Y(0).toFixed(1);
344
- B.forEach(b => { line += " L" + X(b.d).toFixed(1) + " " + Y(b.n).toFixed(1); });
345
- const area = line + " L" + X(B[B.length - 1].d).toFixed(1) + " " + (BURN_Y + BURN_H).toFixed(1) +
346
- " L" + X(B[0].d).toFixed(1) + " " + (BURN_Y + BURN_H).toFixed(1) + " Z";
347
- s += '<path class="burn-area" d="' + area + '"/><path class="burn-line" d="' + line + '"/>' +
348
- '<text class="burn-lab" x="' + (X(B[B.length - 1].d) + 6).toFixed(1) + '" y="' + (Y(maxN) + 4).toFixed(1) + '">' +
349
- maxN + " shipped</text>" +
350
- '<text class="ph-count" x="' + LX + '" y="' + (BURN_Y - 4) + '">cumulative tasks shipped</text>';
351
- }
352
-
353
- /* phase lanes */
354
- phases.forEach((p, i) => {
355
- const y = lanesY + i * (laneH + laneGap);
356
- const x0 = Math.max(LX, X(p.start || TODAY));
357
- const ongoing = p.done < p.total;
358
- const x1 = Math.min(RX, ongoing ? X(TODAY) + 14 : X(p.lastShip || TODAY));
359
- const wBar = Math.max(26, x1 - x0);
360
- const fill = wBar * (p.total ? p.done / p.total : 0);
361
- s += '<g class="ph" data-phase="' + esc(p.id) + '" data-act="phase" data-id="' + esc(p.id) + '">' +
362
- '<text class="ph-name" x="8" y="' + (y + 16) + '">' + esc(p.label) + '</text>' +
363
- '<rect class="ph-track" x="' + x0.toFixed(1) + '" y="' + y + '" width="' + wBar.toFixed(1) + '" height="' + laneH + '" rx="7"/>' +
364
- (fill > 1 ? '<rect class="ph-fill" x="' + x0.toFixed(1) + '" y="' + y + '" width="' + fill.toFixed(1) + '" height="' + laneH + '" rx="7"/>' : "") +
365
- '<text class="ph-count" x="' + (x0 + wBar + 8).toFixed(1) + '" y="' + (y + 16) + '">' + p.done + "/" + p.total + "</text>" +
366
- '<rect class="ph-hit" x="0" y="' + (y - 2) + '" width="' + W + '" height="' + (laneH + 4) + '"/></g>';
367
- });
368
-
369
- /* release axis with date-true clustering */
370
- s += '<line class="ax" x1="' + LX + '" y1="' + axisY + '" x2="' + RX + '" y2="' + axisY + '"/>';
371
- const clusters = [];
372
- CURRELS.slice().reverse().forEach(r => {
373
- const x = r.d ? X(r.d) : RX;
374
- const c = clusters.length && (x - clusters[clusters.length - 1].x) < 16
375
- ? clusters[clusters.length - 1] : null;
376
- if (c) { c.items.push(r); }
377
- else clusters.push({ x, items: [r] });
378
- });
379
- clusters.forEach(c => {
380
- const bad = c.items.some(r => r.cov.unlinked > 0);
381
- const one = c.items.length === 1;
382
- const idAttr = ' data-act="rel" data-id="' + esc(c.items[c.items.length - 1].v) + '" data-cluster="' +
383
- esc(c.items.map(r => r.v).join(",")) + '"';
384
- s += '<g class="rel"' + idAttr + ' transform="translate(' + c.x.toFixed(1) + "," + axisY + ')">' +
385
- '<circle r="' + (one ? 6 : 9) + '"/>' +
386
- (one ? '<text y="-11">' + esc(c.items[0].v) + "</text>"
387
- : '<text class="cnt" y="3.5">' + c.items.length + "</text>") +
388
- (bad ? '<rect class="viol-tick" x="-4" y="12" width="8" height="3" rx="1.5"/>' : "") +
389
- "</g>";
390
- });
391
-
392
- /* today */
393
- const xT = X(TODAY);
394
- s += '<line class="today-line" x1="' + xT.toFixed(1) + '" y1="14" x2="' + xT.toFixed(1) + '" y2="' + (axisY + 18) + '"/>' +
395
- '<text class="today-lab" x="' + (xT + 5).toFixed(1) + '" y="' + (axisY + 30) + '">today · ' + esc(TODAY) + "</text>";
396
-
397
- s += "</svg>";
398
- document.getElementById("roadwrap").innerHTML = s;
399
-
400
- document.getElementById("relstrip").innerHTML = CURRELS.map(r =>
401
- '<button class="rv-chip" data-act="rel" data-id="' + esc(r.v) + '">v' + esc(r.v) +
402
- '<span class="d">' + esc((r.d || "").slice(5)) + "</span></button>").join("");
403
- })();
404
-
405
- /* ═══ 7 · system map ═══════════════════════════════════════════════════ */
406
- function arcPath(r, a0, a1) {
407
- const large = (a1 - a0) > Math.PI ? 1 : 0;
408
- return "M" + (r * Math.cos(a0)).toFixed(1) + " " + (r * Math.sin(a0)).toFixed(1) +
409
- " A" + r + " " + r + " 0 " + large + " 1 " +
410
- (r * Math.cos(a1)).toFixed(1) + " " + (r * Math.sin(a1)).toFixed(1);
411
- }
412
- (function sysmap() {
413
- let s = '<svg id="map" viewBox="0 0 980 640">' +
414
- '<defs><marker id="marr" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="5.5" markerHeight="5.5" orient="auto-start-reverse">' +
415
- '<path d="M0 0L8 4L0 8z" fill="currentColor" opacity=".55"/></marker></defs>';
416
- (D.medges || []).forEach((e, i) => {
417
- const a = MOD[e.from], b = MOD[e.to];
418
- if (!a || !b) return;
419
- const dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy) || 1;
420
- const x1 = a.x + dx / d * (a.r + 6), y1 = a.y + dy / d * (a.r + 6);
421
- const x2 = b.x - dx / d * (b.r + 12), y2 = b.y - dy / d * (b.r + 12);
422
- const mx = (x1 + x2) / 2 - dy / d * 22, my = (y1 + y2) / 2 + dx / d * 22;
423
- s += '<path class="medge" data-from="' + esc(e.from) + '" data-to="' + esc(e.to) + '" data-ei="' + i +
424
- '" d="M' + x1.toFixed(0) + " " + y1.toFixed(0) + " Q" + mx.toFixed(0) + " " + my.toFixed(0) +
425
- " " + x2.toFixed(0) + " " + y2.toFixed(0) + '" stroke-width="' +
426
- Math.min(4.5, 0.7 + e.w * 0.45).toFixed(1) + '" marker-end="url(#marr)"/>';
427
- });
428
- MODS.forEach(m => {
429
- const segs = [
430
- ["a-done", m.done || 0], ["a-active", m.active || 0],
431
- ["a-hold", m.hold || 0], ["a-draft", m.draft || 0],
432
- ].filter(x => x[1] > 0);
433
- const gr = m.r + 5;
434
- let a = -Math.PI / 2, arcs = "";
435
- const gap = segs.length > 1 ? 0.05 : 0.0001;
436
- segs.forEach(sg => {
437
- const span = 2 * Math.PI * sg[1] / m.total - gap;
438
- if (span > 0.02) arcs += '<path class="arc ' + sg[0] + '" stroke-width="4.5" d="' + arcPath(gr, a, a + span) + '"/>';
439
- a += span + gap;
440
- });
441
- const blocked = modBlocked[m.id] || 0;
442
- const fs = m.r > 42 ? 13 : m.r > 26 ? 11.5 : 10.5;
443
- s += '<g class="mnode" data-act="mod" data-id="' + esc(m.id) + '" transform="translate(' + m.x + "," + m.y + ')">' +
444
- '<circle class="body" r="' + m.r + '"/>' + arcs +
445
- ((m.active || 0) > 0 ? '<circle class="pulse" r="3" cx="' + (gr * 0.72).toFixed(0) + '" cy="-' + (gr * 0.72).toFixed(0) + '"/>' +
446
- '<circle class="pulse-halo" r="3" cx="' + (gr * 0.72).toFixed(0) + '" cy="-' + (gr * 0.72).toFixed(0) + '"/>' : "") +
447
- '<text class="nm" y="-1" font-size="' + fs + '">' + esc(m.id) + '</text>' +
448
- '<text class="ct" y="12" font-size="' + (fs - 2) + '">' + (m.done || 0) + "/" + m.total + "</text>" +
449
- (blocked ? '<g class="bbadge" transform="translate(' + (gr * 0.75).toFixed(0) + "," + (gr * 0.72).toFixed(0) + ')">' +
450
- '<circle r="8"/><text y="3">' + blocked + "</text></g>" : "") +
451
- "</g>";
452
- });
453
- s += "</svg>";
454
- document.getElementById("mapcard").innerHTML = s +
455
- '<div class="map-hint">hover a module to trace its couplings · click to focus the whole page on it</div>';
456
-
457
- /* ranking rail */
458
- const openOf = m => m.total - (m.done || 0);
459
- const rows = (items, valFn, color) => {
460
- const max = Math.max.apply(null, items.map(valFn).concat([1]));
461
- return items.map(m =>
462
- '<button class="rrow" data-act="mod" data-id="' + esc(m.id) + '">' +
463
- '<span class="rr-id">' + esc(m.id) + '</span>' +
464
- '<span class="rr-bar"><i style="width:' + pct(valFn(m), max) + "%;background:" + color + '"></i></span>' +
465
- '<span class="rr-n">' + valFn(m) + "</span></button>").join("");
466
- };
467
- const byOpen = MODS.filter(m => openOf(m) > 0).sort((a, b) => openOf(b) - openOf(a)).slice(0, 6);
468
- const byBlocked = MODS.filter(m => modBlocked[m.id]).sort((a, b) => (modBlocked[b.id] || 0) - (modBlocked[a.id] || 0)).slice(0, 6);
469
- const bySize = MODS.slice().sort((a, b) => b.total - a.total).slice(0, 6);
470
- document.getElementById("rank").innerHTML =
471
- '<div class="card rank-block"><h4>Most open work</h4>' + rows(byOpen, openOf, "var(--accent)") + "</div>" +
472
- '<div class="card rank-block"><h4>Most blocked</h4>' +
473
- (byBlocked.length ? rows(byBlocked, m => modBlocked[m.id] || 0, "var(--bad)") :
474
- '<div class="ig-meta">no blocked tasks — clean frontier</div>') + "</div>" +
475
- '<div class="card rank-block"><h4>Largest scope</h4>' + rows(bySize, m => m.total, "var(--draft)") + "</div>";
476
- })();
477
-
478
- function mapFocus(mod) {
479
- const touching = {};
480
- if (mod) {
481
- touching[mod] = 1;
482
- (D.medges || []).forEach(e => {
483
- if (e.from === mod) touching[e.to] = 1;
484
- if (e.to === mod) touching[e.from] = 1;
485
- });
486
- }
487
- document.querySelectorAll("#map .medge").forEach(p => {
488
- const hot = mod && (p.getAttribute("data-from") === mod || p.getAttribute("data-to") === mod);
489
- p.classList.toggle("hot", !!hot);
490
- p.classList.toggle("dim", !!mod && !hot);
491
- });
492
- document.querySelectorAll("#map .mnode").forEach(g => {
493
- const id = g.getAttribute("data-id");
494
- g.classList.toggle("sel", mod === id && S.sel && S.sel.kind === "module" && S.sel.id === id);
495
- g.classList.toggle("dim", !!mod && !touching[id]);
496
- });
497
- }
498
-
499
- /* ═══ 8 · flow (dependency graph) ══════════════════════════════════════ */
500
- function flowScopeIds() {
501
- if (S.flow === "sel" && S.sel) {
502
- if (S.sel.kind === "module")
503
- return TASKS.filter(t => t.m === S.sel.id && (S.flowDone || unfinished(t))).map(t => t.i);
504
- if (S.sel.kind === "phase")
505
- return TASKS.filter(t => t.pg === S.sel.id && (S.flowDone || unfinished(t))).map(t => t.i);
506
- if (S.sel.kind === "task") {
507
- const set = new Set([S.sel.id]);
508
- const up = [S.sel.id], dn = [S.sel.id];
509
- while (up.length) { const id = up.pop(); (BY[id] ? BY[id].d : []).forEach(d => { if (BY[d] && !set.has(d)) { set.add(d); up.push(d); } }); }
510
- while (dn.length) { const id = dn.pop(); (REV[id] || []).forEach(c => { if (!set.has(c)) { set.add(c); dn.push(c); } }); }
511
- return [...set].filter(id => BY[id] && (S.flowDone || unfinished(BY[id]) || id === S.sel.id));
512
- }
513
- if (S.sel.kind === "release") {
514
- const r = RELBYV[S.sel.id];
515
- const set = new Set();
516
- if (r) {
517
- ["features", "fixes", "improvements"].forEach(k => (r.notes[k] || []).forEach(it => (it.ids || []).forEach(id => set.add(id))));
518
- (r.cov.commits || []).forEach(c => (c.ids || []).forEach(id => set.add(id)));
519
- [...set].forEach(id => (BY[id] ? BY[id].d : []).forEach(d => { if (BY[d]) set.add(d); }));
520
- }
521
- return [...set].filter(id => BY[id]);
522
- }
523
- }
524
- if (S.flow === "open")
525
- return TASKS.filter(t => unfinished(t) && ((t.d || []).length || (REV[t.i] || []).length)).map(t => t.i);
526
- /* focus: critical path ∪ blocked ∪ unblocking frontier, scored, capped */
527
- const score = {};
528
- CRIT.forEach((id, i) => { score[id] = Math.max(score[id] || 0, 1000 - i); });
529
- BLOCKED.forEach(t => { score[t.i] = Math.max(score[t.i] || 0, 500 + (REV[t.i] || []).length); });
530
- FRONTIER.forEach(t => {
531
- const outs = (REV[t.i] || []).filter(c => BY[c] && unfinished(BY[c])).length;
532
- if (outs) score[t.i] = Math.max(score[t.i] || 0, 200 + outs);
533
- });
534
- return Object.keys(score).sort((a, b) => score[b] - score[a]).slice(0, 64);
535
- }
536
-
537
- function renderFlowTools(nScoped) {
538
- const selLabel = S.sel ? (S.sel.kind === "task" ? short(S.sel.id) : S.sel.id) : null;
539
- document.getElementById("flowtools").innerHTML =
540
- '<button class="fchip' + (S.flow === "focus" ? " on" : "") + '" data-act="flow-mode" data-id="focus">Focus<span class="n">auto</span></button>' +
541
- (selLabel ? '<button class="fchip' + (S.flow === "sel" ? " on" : "") + '" data-act="flow-mode" data-id="sel">' +
542
- esc(S.sel.kind) + ": " + esc(selLabel) + "</button>" : "") +
543
- '<button class="fchip' + (S.flow === "open" ? " on" : "") + '" data-act="flow-mode" data-id="open">All open<span class="n">' +
544
- TASKS.filter(t => unfinished(t) && ((t.d || []).length || (REV[t.i] || []).length)).length + "</span></button>" +
545
- '<button class="fchip" data-act="flow-done">' + (S.flowDone ? "hide" : "show") + " done</button>";
546
- }
547
-
548
- function renderFlow() {
549
- const ids = flowScopeIds();
550
- const wrap = document.getElementById("flowwrap");
551
- renderFlowTools(ids.length);
552
- if (!ids.length) {
553
- wrap.innerHTML = '<div class="flow-empty">Nothing in this scope — pick a module, phase, or release, or switch to All open.</div>';
554
- document.getElementById("flowfoot").innerHTML = "";
555
- return;
556
- }
557
- const set = new Set(ids);
558
-
559
- /* longest-path layering (cycle-safe) */
560
- const depth = {}, st = {};
561
- function dep(id) {
562
- if (id in depth) return depth[id];
563
- if (st[id]) return 0;
564
- st[id] = 1;
565
- let d = 0;
566
- (BY[id].d || []).forEach(p => { if (set.has(p)) d = Math.max(d, dep(p) + 1); });
567
- st[id] = 0;
568
- return (depth[id] = d);
569
- }
570
- ids.forEach(dep);
571
- const nCols = Math.max.apply(null, ids.map(id => depth[id])) + 1;
572
- let cols = Array.from({ length: nCols }, () => []);
573
- ids.forEach(id => cols[depth[id]].push(id));
574
- cols.forEach(c => c.sort((a, b) => (BY[a].m + a) < (BY[b].m + b) ? -1 : 1));
575
-
576
- /* barycenter sweeps to cut crossings */
577
- const pos = {};
578
- const reindex = () => cols.forEach(c => c.forEach((id, i) => { pos[id] = i; }));
579
- reindex();
580
- for (let pass = 0; pass < 3; pass++) {
581
- for (let ci = 1; ci < nCols; ci++) {
582
- cols[ci].sort((a, b) => bary(a, ci, -1) - bary(b, ci, -1) || (a < b ? -1 : 1)); reindex();
583
- }
584
- for (let ci = nCols - 2; ci >= 0; ci--) {
585
- cols[ci].sort((a, b) => bary(a, ci, 1) - bary(b, ci, 1) || (a < b ? -1 : 1)); reindex();
586
- }
587
- }
588
- function bary(id, ci, dir) {
589
- const nb = dir < 0
590
- ? (BY[id].d || []).filter(p => set.has(p) && depth[p] === ci - 1)
591
- : (REV[id] || []).filter(c => set.has(c) && depth[c] === ci + 1);
592
- if (!nb.length) return pos[id];
593
- return nb.reduce((s, n) => s + pos[n], 0) / nb.length;
594
- }
595
-
596
- const NW = 158, NH = 26, GX = 52, GY = 9, PADX = 18, PADY = 30;
597
- const maxRows = Math.max.apply(null, cols.map(c => c.length));
598
- const Wsvg = PADX * 2 + nCols * NW + (nCols - 1) * GX;
599
- const Hsvg = PADY + maxRows * (NH + GY) + 16;
600
- const xy = {};
601
- cols.forEach((col, ci) => {
602
- const x = PADX + ci * (NW + GX);
603
- const y0 = PADY + (maxRows - col.length) * (NH + GY) / 2;
604
- col.forEach((id, ri) => { xy[id] = { x, y: y0 + ri * (NH + GY) }; });
605
- });
606
-
607
- let edges = "", nodes = "", nEdges = 0, nBlockedE = 0;
608
- ids.forEach(id => {
609
- (BY[id].d || []).forEach(p => {
610
- if (!set.has(p)) return;
611
- const a = xy[p], b = xy[id];
612
- const x1 = a.x + NW, y1 = a.y + NH / 2, x2 = b.x, y2 = b.y + NH / 2;
613
- const blocked = unfinished(BY[id]) && BY[p] && unfinished(BY[p]);
614
- const crit = CRITNEXT[p] === id;
615
- nEdges++; if (blocked) nBlockedE++;
616
- edges += '<path class="fedge' + (blocked ? " blocked" : "") + (crit ? " crit" : "") +
617
- '" data-a="' + esc(p) + '" data-b="' + esc(id) + '" d="M' + x1 + " " + y1 +
618
- " C" + (x1 + GX / 2) + " " + y1 + " " + (x2 - GX / 2) + " " + y2 + " " + x2 + " " + y2 + '"/>';
619
- });
620
- });
621
- ids.forEach(id => {
622
- const t = BY[id], p = xy[id];
623
- const ext = (t.d || []).filter(d => !set.has(d)).length;
624
- const ttl = t.t.length > 21 ? t.t.slice(0, 20) + "…" : t.t;
625
- nodes += '<g class="fnode b-' + t.b + (CRITSET.has(id) ? " crit" : "") + (BLOCKEDSET.has(id) ? " blocked" : "") +
626
- '" data-act="task" data-id="' + esc(id) + '" transform="translate(' + p.x + "," + p.y + ')">' +
627
- '<rect width="' + NW + '" height="' + NH + '" rx="7"/>' +
628
- '<circle class="fdot b-' + t.b + '" cx="12" cy="' + NH / 2 + '" r="4"/>' +
629
- '<text class="fid" x="22" y="11">' + esc(short(id)) + "</text>" +
630
- '<text class="fttl" x="22" y="21">' + esc(ttl) + "</text>" +
631
- (t.p === "p0" ? '<text class="pflag" x="' + (NW - 18) + '" y="11">P0</text>' : "") +
632
- (ext ? '<text class="stub" x="-14" y="' + (NH / 2 + 3) + '">◂' + ext + "</text>" : "") +
633
- "</g>";
634
- });
635
-
636
- wrap.innerHTML = '<svg id="flow" width="' + Wsvg + '" height="' + Hsvg + '" viewBox="0 0 ' + Wsvg + " " + Hsvg + '">' +
637
- '<text class="col-lab" x="' + PADX + '" y="16">UPSTREAM (build first)</text>' +
638
- '<text class="col-lab" x="' + (Wsvg - PADX) + '" y="16" text-anchor="end">DOWNSTREAM (unlocked later)</text>' +
639
- edges + nodes + "</svg>";
640
-
641
- document.getElementById("flowfoot").innerHTML =
642
- "<span><b>" + ids.length + "</b> tasks · <b>" + nEdges + "</b> edges in view</span>" +
643
- '<span class="legend"><i style="background:var(--done)"></i>done <i style="background:var(--active)"></i>active ' +
644
- '<i style="background:var(--draft)"></i>draft <i style="background:var(--hold)"></i>hold</span>' +
645
- '<span style="color:var(--bad)">dashed red = blocking edge (' + nBlockedE + ")</span>" +
646
- '<span style="color:var(--accent-ink)">thick amber = critical path</span>' +
647
- "<span>hover = trace the full cone · click = task record · ◂n = deps outside this view</span>";
648
- }
649
-
650
- function flowCone(id, on) {
651
- const svg = document.getElementById("flow");
652
- if (!svg) return;
653
- if (!on) {
654
- svg.querySelectorAll(".dim, .hot").forEach(n => n.classList.remove("dim", "hot"));
655
- return;
656
- }
657
- const cone = new Set([id]);
658
- const up = [id], dn = [id];
659
- while (up.length) { const x = up.pop(); (BY[x] ? BY[x].d : []).forEach(d => { if (!cone.has(d)) { cone.add(d); up.push(d); } }); }
660
- while (dn.length) { const x = dn.pop(); (REV[x] || []).forEach(c => { if (!cone.has(c)) { cone.add(c); dn.push(c); } }); }
661
- svg.querySelectorAll(".fnode").forEach(n => {
662
- n.classList.toggle("dim", !cone.has(n.getAttribute("data-id")));
663
- });
664
- svg.querySelectorAll(".fedge").forEach(e => {
665
- const inCone = cone.has(e.getAttribute("data-a")) && cone.has(e.getAttribute("data-b"));
666
- e.classList.toggle("hot", inCone);
667
- e.classList.toggle("dim", !inCone);
668
- });
669
- }
670
-
671
- /* ═══ 9 · ledger ═══════════════════════════════════════════════════════ */
672
- function taskChip(id) {
673
- const t = BY[id];
674
- return '<button class="tchip" data-act="task" data-id="' + id + '"><span class="dt ' +
675
- (t ? "b-" + t.b : "ghost") + '"></span>' + short(id) + "</button>";
676
- }
677
- /* token-substitution so canonical ids, shorthand ids, and PR refs never nest */
678
- function chipify(text) {
679
- const toks = [];
680
- const tok = html => { toks.push(html); return "\u0000" + (toks.length - 1) + "\u0000"; };
681
- let s = esc(text);
682
- s = s.replace(/\bTASK-[A-Z][A-Z0-9]*-\d+\b/g, id => tok(taskChip(id)));
683
- s = s.replace(/\b([A-Z]{2,10})-(\d{1,4})\b/g, (m0, pre, n) => {
684
- const a = "TASK-" + pre + "-" + String(n).padStart(3, "0"), b = "TASK-" + pre + "-" + n;
685
- const id = BY[a] ? a : BY[b] ? b : null;
686
- return id ? tok(taskChip(id)) : m0;
687
- });
688
- if (GH) s = s.replace(/#(\d{2,6})\b/g, (m0, n) =>
689
- tok('<a class="prlink" target="_blank" rel="noopener" href="' + GH + "/pull/" + n + '">#' + n + "</a>"));
690
- return s.replace(/\u0000(\d+)\u0000/g, (m0, i) => toks[+i]);
691
- }
692
- function covBarHtml(cov) {
693
- const tot = (cov.linked + cov.exempt + cov.unlinked) || 1;
694
- return '<div class="covwrap"><div class="covbar">' +
695
- '<i class="c-linked" style="width:' + (100 * cov.linked / tot) + '%"></i>' +
696
- '<i class="c-exempt" style="width:' + (100 * cov.exempt / tot) + '%"></i>' +
697
- '<i class="c-unlinked" style="width:' + (100 * cov.unlinked / tot) + '%"></i></div>' +
698
- '<span class="covlab' + (cov.unlinked ? " bad" : "") + '">' + cov.linked + " linked · " +
699
- cov.exempt + " exempt" + (cov.unlinked ? " · " + cov.unlinked + " UNLINKED" : "") + "</span></div>";
700
- }
701
- function notesCols(notes) {
702
- const col = (key, label, cls) => {
703
- const items = notes[key] || [];
704
- return '<div class="ncol ' + cls + '"><h4><span class="k"></span>' + label + " · " + items.length + "</h4>" +
705
- (items.length ? items.map(it => '<div class="nitem">' + chipify(it.x) + "</div>").join("")
706
- : '<div class="nitem"><span class="none">none</span></div>') + "</div>";
707
- };
708
- return '<div class="notes3">' + col("features", "Features", "feat") +
709
- col("fixes", "Fixes", "fix") + col("improvements", "Improvements", "impr") + "</div>";
710
- }
711
- const commitStore = {};
712
- function commitsHtml(cov, storeKey) {
713
- commitStore[storeKey] = cov.commits || [];
714
- const n = (cov.commits || []).length;
715
- if (!n) return "";
716
- return '<details class="commits" data-ck="' + esc(storeKey) + '"><summary>' + n + " commits · " +
717
- cov.linked + " linked · " + cov.exempt + " exempt" +
718
- (cov.unlinked ? ' · <span class="vio">' + cov.unlinked + " unlinked — rule violations</span>" : " · fully traced") +
719
- "</summary><div class=\"cbody\"></div></details>";
720
- }
721
- function renderCommitRows(box, key) {
722
- if (box.dataset.done) return;
723
- box.dataset.done = "1";
724
- const rows = commitStore[key] || [];
725
- box.innerHTML = rows.map(c =>
726
- '<div class="crow ' + c.k + '">' + extA(ghc(c.h), "h", esc(c.h), "open commit on GitHub") +
727
- '<span class="s">' + chipify(c.s) + "</span>" +
728
- (c.k === "unlinked" ? '<span class="flag">NO TASK LINK</span>' :
729
- c.k === "exempt" ? '<span class="flag ok">exempt</span>' :
730
- c.via === "ledger" ? '<span class="flag ok" title="linked via commit-links.yaml">ledger</span>' : "") +
731
- "</div>").join("");
732
- }
733
-
734
- (function ledger() {
735
- let html =
736
- '<div class="card rule-banner"><span class="ic">' + ICONS.link + "</span>" +
737
- '<div class="rule-txt"><b>The rule</b>' + esc(D.rule) +
738
- '<div class="enf"><b>Enforcement:</b> ' + esc(D.enforcement) + "</div></div>" +
739
- '<div class="rule-stats">' +
740
- '<div class="rs good"><b>' + num(covCur.linked) + "</b><span>linked</span></div>" +
741
- '<div class="rs"><b>' + num(covCur.exempt) + "</b><span>exempt</span></div>" +
742
- '<div class="rs bad"><b>' + num(covCur.unlinked) + "</b><span>unlinked</span></div>" +
743
- '<div class="rs' + (covPct >= 85 ? " good" : " bad") + '"><b>' + covPct + "%</b><span>coverage</span></div></div></div>";
744
-
745
- /* trend (current epoch only) */
746
- const asc = CURRELS.slice().reverse();
747
- const bw = Math.max(14, Math.min(38, Math.floor(1100 / Math.max(1, asc.length)) - 6));
748
- let tx = 10, bars = "";
749
- asc.forEach(r => {
750
- const denom = r.cov.linked + r.cov.unlinked;
751
- const p = denom ? Math.round(100 * r.cov.linked / denom) : 100;
752
- const cls = p < 50 ? "low" : p < 85 ? "mid" : "";
753
- bars += '<g class="tb ' + cls + '" data-act="rel" data-id="' + esc(r.v) + '">' +
754
- '<rect class="bgb" x="' + tx + '" y="8" width="' + bw + '" height="44" rx="3"/>' +
755
- '<rect class="fgb" x="' + tx + '" y="' + (8 + 44 * (1 - p / 100)).toFixed(1) + '" width="' + bw +
756
- '" height="' + (44 * p / 100).toFixed(1) + '" rx="3"/>' +
757
- '<text x="' + (tx + bw / 2) + '" y="62">' + esc(r.v.replace(/\.0$/, "")) + "</text>" +
758
- "<title>v" + esc(r.v) + " — " + p + "% of gated commits linked</title></g>";
759
- tx += bw + 6;
760
- });
761
- html += '<div class="card trend-card"><h4>Commit-to-task coverage per release (green ≥ 85%)</h4>' +
762
- '<svg id="trend" viewBox="0 0 ' + (tx + 10) + ' 70" preserveAspectRatio="xMinYMid meet">' + bars + "</svg></div>";
763
-
764
- html += '<div class="ledger" id="relcards">';
765
- const unrel = D.unreleased || { notes: nextNotes, cov: { commits: [], linked: 0, exempt: 0, unlinked: 0 } };
766
- if ((unrel.cov.commits || []).length || nextCount) {
767
- html += '<div class="card rel-card" id="rel-unreleased"><div class="rel-hd">' +
768
- '<span class="rv">Unreleased</span><span class="rd">staged for the next cut</span>' +
769
- '<span class="cnt feat">' + unrel.notes.features.length + ' features</span>' +
770
- '<span class="cnt fix">' + unrel.notes.fixes.length + ' fixes</span>' +
771
- '<span class="cnt impr">' + unrel.notes.improvements.length + ' improvements</span>' +
772
- '<span class="sp"></span>' + covBarHtml(unrel.cov) + "</div>" +
773
- notesCols(unrel.notes) + commitsHtml(unrel.cov, "unreleased") + "</div>";
774
- }
775
-
776
- /* featured cards only for releases that actually carry notes */
777
- const featured = CURRELS.filter(hasNotes);
778
- const plumbing = CURRELS.filter(r => !hasNotes(r));
779
- const FOLD = 6;
780
- featured.forEach((r, i) => {
781
- html += '<div class="card rel-card' + (i >= FOLD ? " paged" : "") + '" id="rel-' + esc(r.v).replace(/\./g, "-") +
782
- '"><div class="rel-hd">' +
783
- extA(relHref(r), "rv", "v" + esc(r.v), "open on GitHub") +
784
- '<span class="rd">' + esc(r.d || "") + "</span>" +
785
- '<span class="cnt feat">' + r.notes.features.length + "</span>" +
786
- '<span class="cnt fix">' + r.notes.fixes.length + "</span>" +
787
- '<span class="cnt impr">' + r.notes.improvements.length + "</span>" +
788
- '<span class="sp"></span>' + covBarHtml(r.cov) + "</div>" +
789
- notesCols(r.notes) + commitsHtml(r.cov, r.v) + "</div>";
790
- });
791
- if (featured.length > FOLD)
792
- html += '<button class="show-more" id="relmore" data-act="show-rels">Show ' +
793
- (featured.length - FOLD) + " older releases with notes</button>";
794
-
795
- /* zero-note plumbing releases: one compact group, one line each */
796
- if (plumbing.length) {
797
- html += '<div class="card rel-slimcard"><details class="commits" open><summary>' + plumbing.length +
798
- " releases without notes (version plumbing) — the changelog gap the rule closes</summary>" +
799
- plumbing.map(r =>
800
- '<div class="slimrow" id="rel-' + esc(r.v).replace(/\./g, "-") + '" data-act="rel" data-id="' + esc(r.v) + '">' +
801
- extA(relHref(r), "rv rv-s", "v" + esc(r.v)) +
802
- '<span class="rd">' + esc(r.d || "") + "</span>" +
803
- '<span class="slim-cov' + (r.cov.unlinked ? " bad" : "") + '">' + (r.cov.commits || []).length + " commits · " +
804
- r.cov.linked + " linked" + (r.cov.unlinked ? " · " + r.cov.unlinked + " UNLINKED" : "") + "</span></div>").join("") +
805
- "</details></div>";
806
- }
807
-
808
- /* first-epoch history, folded */
809
- RELS.filter(r => r.lg).forEach(r => {
810
- html += '<div class="card rel-card" id="rel-first-epoch"><div class="rel-hd">' +
811
- '<span class="rv">first epoch</span><span class="rd">history before the version reset of ' + esc(r.d || "") + "</span>" +
812
- '<span class="sp"></span>' + covBarHtml(r.cov) + "</div>" +
813
- '<div class="ncol"><div class="nitem"><span class="none">Version numbers up to v1.9.1 were reissued after the ' +
814
- esc(r.d || "") + " rollback, so this whole span is kept as one entry. The enforcement gate applies from the cutoff commit forward; this history stays visible, not counted.</span></div></div>" +
815
- commitsHtml(r.cov, r.v) + "</div>";
816
- });
817
- html += "</div>";
818
- document.getElementById("ledgerwrap").innerHTML = html;
819
- })();
820
-
821
- /* ═══ 10 · index (filtered, capped, lazy) ══════════════════════════════ */
822
- const IDX = { f: "open", cap: 30 };
823
- const IDXF = {
824
- all: { label: "All", fn: () => true },
825
- open: { label: "Open", fn: t => t.b !== "done" },
826
- blocked: { label: "Blocked", fn: t => BLOCKEDSET.has(t.i) },
827
- ready: { label: "Ready", fn: t => FRONTSET.has(t.i) },
828
- done: { label: "Done", fn: t => t.b === "done" },
829
- };
830
- function idxMatch(t) {
831
- if (S.sel && S.sel.kind === "phase" && t.pg !== S.sel.id) return false;
832
- return IDXF[IDX.f].fn(t);
833
- }
834
- function renderIdxTools() {
835
- document.getElementById("idxtools").innerHTML = Object.keys(IDXF).map(k =>
836
- '<button class="fchip' + (IDX.f === k ? " on" : "") + '" data-act="idx-filter" data-id="' + k + '">' +
837
- IDXF[k].label + '<span class="n">' + TASKS.filter(t => IDXF[k].fn(t)).length + "</span></button>").join("");
838
- }
839
- function renderIndex() {
840
- const openMods = new Set([...document.querySelectorAll(".idx-group[open]")].map(g => g.getAttribute("data-mod")));
841
- const groups = MODS.map(m => ({ m, n: TASKS.filter(t => t.m === m.id && idxMatch(t)).length }))
842
- .filter(g => g.n > 0)
843
- .sort((a, b) => b.n - a.n || b.m.total - a.m.total);
844
- document.getElementById("idxcard").innerHTML = groups.map(g => {
845
- const m = g.m;
846
- const segs = [["done", m.done || 0], ["active", m.active || 0], ["hold", m.hold || 0], ["draft", m.draft || 0]];
847
- const bar = segs.map(sg => '<i style="width:' + (100 * sg[1] / m.total) + "%;background:var(--" +
848
- (sg[0] === "draft" ? "draft" : sg[0]) + ')"></i>').join("");
849
- return '<details class="idx-group" data-mod="' + esc(m.id) + '"' + (openMods.has(m.id) ? " open" : "") + "><summary>" +
850
- '<svg class="car" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><path d="m6 3.5 4.5 4.5L6 12.5"/></svg>' +
851
- '<span class="ig-name">' + esc(m.id) + '</span>' +
852
- '<span class="ig-fc">' + g.n + " " + (IDX.f === "all" ? "task" : IDXF[IDX.f].label.toLowerCase()) + (g.n > 1 && IDX.f === "all" ? "s" : "") + "</span>" +
853
- '<span class="ig-meta">' + m.total + " total · " + (m.done || 0) + " done" +
854
- (modBlocked[m.id] ? " · " + modBlocked[m.id] + " blocked" : "") + "</span>" +
855
- '<span class="ig-bar">' + bar + "</span></summary><div class=\"ig-body\"></div></details>";
856
- }).join("") || '<div class="flow-empty">No task matches this filter.</div>';
857
- renderIdxTools();
858
- document.querySelectorAll(".idx-group[open]").forEach(fillGroup);
859
- }
860
- function fillGroup(det, showAll) {
861
- const body = det.querySelector(".ig-body");
862
- const mod = det.getAttribute("data-mod");
863
- const order = { active: 0, hold: 1, draft: 2, done: 3 };
864
- const list = TASKS.filter(t => t.m === mod && idxMatch(t))
865
- .sort((a, b) => (order[a.b] - order[b.b]) || (a.i < b.i ? -1 : 1));
866
- const shown = showAll ? list : list.slice(0, IDX.cap);
867
- body.innerHTML = shown.map(t =>
868
- '<button class="irow" data-act="task" data-id="' + esc(t.i) + '">' +
869
- '<span class="iid">' + esc(short(t.i)) + '</span>' +
870
- '<span class="ittl">' + esc(t.t) + '</span>' +
871
- '<span class="pill b-' + t.b + '"><i></i>' + esc(stLabel(t.s)) + "</span>" +
872
- '<span class="prio ' + esc(t.p) + '">' + esc((t.p || "").toUpperCase()) + "</span>" +
873
- '<span class="ideps">' + ((t.d || []).length ? "◂" + t.d.length : "") +
874
- ((REV[t.i] || []).length ? " ▸" + REV[t.i].length : "") + "</span></button>").join("") +
875
- (list.length > shown.length
876
- ? '<button class="show-more" data-act="idx-more" data-id="' + esc(mod) + '">Show ' + (list.length - shown.length) + " more</button>"
877
- : "");
878
- }
879
-
880
- /* ═══ 11 · drawer ══════════════════════════════════════════════════════ */
881
- const drawer = document.getElementById("drawer");
882
- const scrim = document.getElementById("scrim");
883
- function chipsOf(idList) {
884
- if (!idList || !idList.length) return '<span class="ig-meta">none</span>';
885
- return idList.map(id => {
886
- const t = BY[id];
887
- return '<button class="tchip" data-act="task" data-id="' + esc(id) + '" title="' +
888
- esc(t ? t.t + " [" + stLabel(t.s) + "]" : "not in live corpus (archived or future)") + '">' +
889
- '<span class="dt ' + (t ? "b-" + t.b : "ghost") + '"></span>' + esc(short(id)) + "</button>";
890
- }).join("");
891
- }
892
- function openTask(id) {
893
- const t = BY[id];
894
- if (!t) return;
895
- const unmet = unmetDeps(t);
896
- const meta = [
897
- ["status", stLabel(t.s)], ["type", t.c], ["priority", (t.p || "—").toUpperCase()],
898
- ["module", t.m], ["phase", t.ph || "—"], ["owner", t.o || "—"],
899
- ["created", t.cr || "—"], ["shipped", t.sh || "—"], ["effort", t.e ? t.e + "h" : "—"],
900
- ];
901
- drawer.innerHTML =
902
- '<div class="d-hd"><div><div class="d-id">' + esc(t.i) + '</div><div class="d-ttl">' + esc(t.t) + "</div>" +
903
- '<div style="margin-top:7px"><span class="pill b-' + t.b + '"><i></i>' + esc(stLabel(t.s)) + "</span>" +
904
- (CRITSET.has(id) ? ' <span class="pill" style="background:var(--accent-soft);color:var(--accent-ink)">critical path</span>' : "") +
905
- (FRONTSET.has(id) ? ' <span class="pill b-done"><i></i>ready to start</span>' : "") + "</div></div>" +
906
- '<button class="d-close" data-act="close-drawer" aria-label="Close">' + ICONS.x + "</button></div>" +
907
- '<div class="d-body">' +
908
- '<div class="d-meta">' + meta.map(m => '<div class="dm"><div class="k">' + m[0] + '</div><div class="v">' + esc(m[1]) + "</div></div>").join("") + "</div>" +
909
- (t.sm ? '<div class="d-sum">' + esc(t.sm) + "</div>" : "") +
910
- (unmet.length ? '<div class="d-warn">' + ICONS.warn + "<span><b>Blocked.</b> Waiting on " +
911
- unmet.map(esc).join(", ") + "</span></div>" : "") +
912
- '<div class="d-sec"><h4>Depends on · ' + (t.d || []).length + '</h4><div class="chips">' + chipsOf(t.d) + "</div></div>" +
913
- '<div class="d-sec"><h4>Unblocks · ' + (REV[t.i] || []).length + '</h4><div class="chips">' + chipsOf(REV[t.i]) + "</div></div>" +
914
- ((t.rl || []).length ? '<div class="d-sec"><h4>Related</h4><div class="chips">' + chipsOf(t.rl) + "</div></div>" : "") +
915
- '<div class="d-sec"><h4>Cited in releases · ' + (RELOF[id] || []).length + "</h4>" +
916
- ((RELOF[id] || []).length ? (RELOF[id] || []).map(v =>
917
- '<button class="d-rel" data-act="rel" data-id="' + esc(v) + '" style="width:100%;text-align:left">' +
918
- '<span class="h">' + esc(relLabel(v)) + "</span><span>" +
919
- esc((RELBYV[v] || {}).d || "") + "</span></button>").join("") : '<span class="ig-meta">not cited yet — will appear here when a release note or commit references it</span>') + "</div>" +
920
- '<div class="d-sec"><h4>Commits citing this task · ' + (COMOF[id] || []).length + "</h4>" +
921
- ((COMOF[id] || []).slice(0, 8).map(c => '<div class="d-rel">' + extA(ghc(c.h), "h", esc(c.h), "open commit on GitHub") +
922
- "<span>" + esc(c.s) + "</span></div>").join("") || '<span class="ig-meta">none yet</span>') + "</div>" +
923
- '<div class="d-sec" id="dw-spec"><h4>Spec</h4><div id="dw-spec-body"><p class="ig-meta">Loading…</p></div></div>' +
924
- '<div class="d-links">' +
925
- '<a class="btn" target="_blank" rel="noopener" href="' + esc((D.specBase || '') + t.k + "/spec.md") + '">' + ICONS.open + " Open spec</a>" +
926
- '<button class="btn" data-act="cone" data-id="' + esc(t.i) + '">' + ICONS.graph + " Trace in graph</button>" +
927
- '<button class="btn" data-act="mod" data-id="' + esc(t.m) + '">' + ICONS.graph + " Focus module " + esc(t.m) + "</button>" +
928
- "</div></div>";
929
- drawer.classList.add("open");
930
- scrim.classList.add("open");
931
- setHash("t/" + id);
932
- loadSpec(t);
933
- }
934
- function loadSpec(t) {
935
- const body = document.getElementById("dw-spec-body");
936
- if (!body) return;
937
- if (!t.sp) {
938
- body.innerHTML = '<p class="ig-meta">No spec chunk shipped with this page. Use Open spec for the markdown source.</p>';
939
- return;
940
- }
941
- const W = window.CS_SPEC || (window.CS_SPEC = {});
942
- function paint() {
943
- body.innerHTML = '<div class="spec">' + W[t.i] + "</div>";
944
- }
945
- if (W[t.i]) { paint(); return; }
946
- body.innerHTML = '<p class="ig-meta">Loading the full spec …</p>';
947
- const dir = D.specDir || "data/task";
948
- const sc = document.createElement("script");
949
- sc.src = dir + "/" + t.i + ".js";
950
- sc.onerror = function () {
951
- body.innerHTML = '<p class="ig-meta">The spec chunk did not load. Use Open spec for the markdown source.</p>';
952
- };
953
- sc.onload = function () { if (W[t.i]) paint(); else sc.onerror(); };
954
- document.head.appendChild(sc);
955
- }
956
- function closeDrawer() {
957
- if (!drawer.classList.contains("open")) return;
958
- drawer.classList.remove("open");
959
- scrim.classList.remove("open");
960
- /* restore the pinned selection's deep link, or clear the task link */
961
- setHash(S.sel && S.sel.kind !== "task" ? selHash() : "");
962
- }
963
-
964
- /* ═══ 12 · selection + routing ═════════════════════════════════════════ */
965
- const selslot = document.getElementById("selslot");
966
- function renderSelChip() {
967
- selslot.innerHTML = S.sel
968
- ? '<span class="selchip"><button data-act="sel-go" title="Jump to it">' +
969
- esc(S.sel.kind) + ": " + esc(S.sel.kind === "task" ? short(S.sel.id) : S.sel.id) + "</button>" +
970
- '<button class="x" data-act="clear-sel" aria-label="Clear selection">✕</button></span>'
971
- : "";
972
- }
973
- function setHash(h) {
974
- const target = h ? "#" + h : "";
975
- if (target === location.hash) return;
976
- S.lastSetHash = target || "#";
977
- location.hash = h;
978
- }
979
- function selHash() {
980
- if (!S.sel) return "";
981
- const p = { task: "t/", module: "m/", phase: "p/", release: "r/" }[S.sel.kind];
982
- return p + S.sel.id;
983
- }
984
- /* dim map nodes/edges to a set of module ids (phase and release selections) */
985
- function mapDimSet(set) {
986
- document.querySelectorAll("#map .mnode").forEach(g => {
987
- g.classList.remove("sel");
988
- g.classList.toggle("dim", !!set && !set.has(g.getAttribute("data-id")));
989
- });
990
- document.querySelectorAll("#map .medge").forEach(p => {
991
- const on = set && set.has(p.getAttribute("data-from")) && set.has(p.getAttribute("data-to"));
992
- p.classList.remove("hot");
993
- p.classList.toggle("dim", !!set && !on);
994
- });
995
- }
996
- function relCitedIds(v) {
997
- const r = v === "unreleased" ? { notes: (D.unreleased || {}).notes || {}, cov: (D.unreleased || {}).cov || {} } : RELBYV[v];
998
- const set = new Set();
999
- if (!r) return set;
1000
- ["features", "fixes", "improvements"].forEach(k => ((r.notes || {})[k] || []).forEach(it => (it.ids || []).forEach(id => set.add(id))));
1001
- ((r.cov || {}).commits || []).forEach(c => (c.ids || []).forEach(id => set.add(id)));
1002
- return set;
1003
- }
1004
- function clearMarks() {
1005
- document.querySelectorAll(".rel-card.sel, .slimrow.sel, .rv-chip.sel, #roadsvg .rel.sel, #roadsvg g.ph.sel, #roadsvg g.ph.dim")
1006
- .forEach(n => n.classList.remove("sel", "dim"));
1007
- }
1008
- /* jump target for the selection chip and for second clicks — the ONLY places that scroll */
1009
- function selGo() {
1010
- if (!S.sel) return;
1011
- if (S.sel.kind === "module") document.getElementById("sysmap").scrollIntoView({ block: "start" });
1012
- else if (S.sel.kind === "phase" || S.sel.kind === "task") document.getElementById("flowband").scrollIntoView({ block: "start" });
1013
- else if (S.sel.kind === "release") {
1014
- const card = document.getElementById(S.sel.id === "unreleased" ? "rel-unreleased" : "rel-" + S.sel.id.replace(/\./g, "-"));
1015
- if (card) {
1016
- if (card.classList.contains("paged")) revealRels();
1017
- const det = card.closest("details");
1018
- if (det) det.open = true;
1019
- card.scrollIntoView({ block: "center" });
1020
- }
1021
- }
1022
- }
1023
- function revealRels() {
1024
- document.querySelectorAll(".rel-card.paged").forEach(c => c.classList.remove("paged"));
1025
- const b = document.getElementById("relmore");
1026
- if (b) b.remove();
1027
- }
1028
- /* selections update every band IN PLACE — nothing scrolls unless asked (selGo / deep link) */
1029
- function selectModule(id, opts) {
1030
- if (!MOD[id]) return;
1031
- const again = S.sel && S.sel.kind === "module" && S.sel.id === id;
1032
- S.sel = { kind: "module", id };
1033
- S.flow = "sel";
1034
- clearMarks(); mapFocus(id); renderSelChip(); renderFlow(); renderIndex();
1035
- document.querySelectorAll(".idx-group").forEach(g => {
1036
- if (g.getAttribute("data-mod") === id) { g.open = true; fillGroup(g); }
1037
- });
1038
- setHash("m/" + id);
1039
- if ((opts && opts.scroll) || again) selGo();
1040
- }
1041
- function selectPhase(id, opts) {
1042
- const again = S.sel && S.sel.kind === "phase" && S.sel.id === id;
1043
- S.sel = { kind: "phase", id };
1044
- S.flow = "sel";
1045
- clearMarks(); renderSelChip(); renderFlow(); renderIndex();
1046
- document.querySelectorAll("#roadsvg g.ph").forEach(g => {
1047
- const on = g.getAttribute("data-phase") === id;
1048
- g.classList.toggle("sel", on);
1049
- g.classList.toggle("dim", !on);
1050
- });
1051
- mapDimSet(new Set(TASKS.filter(t => t.pg === id).map(t => t.m)));
1052
- setHash("p/" + id);
1053
- if ((opts && opts.scroll) || again) selGo();
1054
- }
1055
- function selectRelease(v, opts) {
1056
- const again = S.sel && S.sel.kind === "release" && S.sel.id === v;
1057
- S.sel = { kind: "release", id: v };
1058
- clearMarks(); renderSelChip();
1059
- document.querySelectorAll(".rv-chip").forEach(c =>
1060
- c.classList.toggle("sel", c.getAttribute("data-id") === v));
1061
- document.querySelectorAll("#roadsvg .rel").forEach(g =>
1062
- g.classList.toggle("sel", (g.getAttribute("data-cluster") || "").split(",").indexOf(v) >= 0));
1063
- const card = document.getElementById(v === "unreleased" ? "rel-unreleased" : "rel-" + v.replace(/\./g, "-"));
1064
- if (card) card.classList.add("sel");
1065
- const cited = relCitedIds(v);
1066
- mapDimSet(cited.size ? new Set([...cited].filter(id => BY[id]).map(id => BY[id].m)) : null);
1067
- if (S.flow === "sel") renderFlow(); else renderFlowTools();
1068
- setHash("r/" + v);
1069
- if ((opts && opts.scroll) || again) selGo();
1070
- }
1071
- function clearSel() {
1072
- S.sel = null;
1073
- if (S.flow === "sel") S.flow = "focus";
1074
- renderSelChip(); mapFocus(null); mapDimSet(null); clearMarks(); renderFlow(); renderIndex();
1075
- setHash("");
1076
- }
1077
- function applyHash() {
1078
- if (S.lastSetHash && (location.hash === S.lastSetHash || (S.lastSetHash === "#" && !location.hash))) {
1079
- S.lastSetHash = null;
1080
- return;
1081
- }
1082
- S.lastSetHash = null;
1083
- const h = decodeURIComponent(location.hash.replace(/^#/, ""));
1084
- if (!h) return;
1085
- /* status-hub@2 bookmarks stay alive */
1086
- const LEGACY_BAND = {
1087
- board: "indexband", table: "indexband", timeline: "ledger",
1088
- roadmap: "roadmap", backlog: "indexband", changelog: "ledger",
1089
- };
1090
- if (LEGACY_BAND[h]) {
1091
- const el = document.getElementById(LEGACY_BAND[h]);
1092
- if (el) el.scrollIntoView({ block: "start" });
1093
- return;
1094
- }
1095
- if (h.indexOf("task/") === 0 && BY[h.slice(5)]) { openTask(h.slice(5)); return; }
1096
- const kind = h.slice(0, 2), id = h.slice(2);
1097
- if (kind === "t/" && BY[id]) openTask(id);
1098
- else if (kind === "m/") selectModule(id, { scroll: true });
1099
- else if (kind === "p/") selectPhase(id, { scroll: true });
1100
- else if (kind === "r/") selectRelease(id, { scroll: true });
1101
- }
1102
-
1103
- /* ═══ 13 · search ══════════════════════════════════════════════════════ */
1104
- const qEl = document.getElementById("q");
1105
- const drop = document.getElementById("srdrop");
1106
- let srIdx = 0, srItems = [];
1107
- function doSearch(q) {
1108
- q = q.trim().toLowerCase();
1109
- if (!q) { drop.hidden = true; return; }
1110
- const hits = [];
1111
- const push = (kind, id, t, score) => hits.push({ kind, id, t, score });
1112
- TASKS.forEach(t => {
1113
- const idl = t.i.toLowerCase(), tl = t.t.toLowerCase();
1114
- if (idl.includes(q)) push("task", t.i, t.t, idl.indexOf(q) === 0 ? 0 : idl.startsWith("task-" + q) ? 1 : 3);
1115
- else if (tl.includes(q)) push("task", t.i, t.t, 5 + tl.indexOf(q) / 200);
1116
- });
1117
- MODS.forEach(m => { if (m.id.toLowerCase().includes(q)) push("module", m.id, m.total + " tasks", 2); });
1118
- RELS.forEach(r => { if (("v" + r.v).includes(q) || r.v.includes(q)) push("release", r.v, r.d || "", 2); });
1119
- hits.sort((a, b) => a.score - b.score);
1120
- srItems = hits.slice(0, 10);
1121
- srIdx = 0;
1122
- drop.hidden = false;
1123
- drop.innerHTML = srItems.length ? srItems.map((h, i) =>
1124
- '<button class="sr-item' + (i === srIdx ? " on" : "") + '" data-sri="' + i + '">' +
1125
- '<span class="sr-kind">' + h.kind + '</span><span class="sr-id">' +
1126
- esc(h.kind === "release" ? "v" + h.id : h.id) + '</span><span class="sr-t">' + esc(h.t) + "</span></button>").join("")
1127
- : '<div class="sr-empty">No match for "' + esc(q) + '"</div>';
1128
- }
1129
- function srGo(i) {
1130
- const h = srItems[i];
1131
- if (!h) return;
1132
- drop.hidden = true; qEl.blur();
1133
- if (h.kind === "task") openTask(h.id);
1134
- else if (h.kind === "module") selectModule(h.id, { scroll: true });
1135
- else selectRelease(h.id, { scroll: true });
1136
- }
1137
- qEl.addEventListener("input", () => doSearch(qEl.value));
1138
- qEl.addEventListener("keydown", e => {
1139
- if (drop.hidden) return;
1140
- if (e.key === "ArrowDown") { srIdx = Math.min(srItems.length - 1, srIdx + 1); }
1141
- else if (e.key === "ArrowUp") { srIdx = Math.max(0, srIdx - 1); }
1142
- else if (e.key === "Enter") { srGo(srIdx); e.preventDefault(); return; }
1143
- else if (e.key === "Escape") { drop.hidden = true; return; }
1144
- else return;
1145
- e.preventDefault();
1146
- drop.querySelectorAll(".sr-item").forEach((n, i) => n.classList.toggle("on", i === srIdx));
1147
- });
1148
- document.addEventListener("keydown", e => {
1149
- if (e.key === "/" && document.activeElement !== qEl && !/input|textarea/i.test(document.activeElement.tagName)) {
1150
- e.preventDefault(); qEl.focus(); qEl.select();
1151
- }
1152
- if (e.key === "Escape") {
1153
- if (!drop.hidden) { drop.hidden = true; return; }
1154
- if (drawer.classList.contains("open")) { closeDrawer(); return; }
1155
- if (S.sel) clearSel();
1156
- }
1157
- });
1158
- document.addEventListener("click", e => {
1159
- if (!e.target.closest(".search")) drop.hidden = true;
1160
- });
1161
-
1162
- /* ═══ 14 · theme ═══════════════════════════════════════════════════════ */
1163
- function applyTheme(th) {
1164
- document.body.setAttribute("data-theme", th);
1165
- document.querySelector(".theme-btn").innerHTML = th === "paper" ? ICONS.moon : ICONS.sun;
1166
- try { localStorage.setItem("sv3-theme", th); } catch (err) { /* file:// may deny */ }
1167
- }
1168
- try { const saved = localStorage.getItem("sv3-theme"); if (saved) applyTheme(saved); } catch (err) { /* ignore */ }
1169
- /* shareable override: index.html?theme=night */
1170
- try {
1171
- const th = new URLSearchParams(location.search).get("theme");
1172
- if (th === "night" || th === "paper") applyTheme(th);
1173
- } catch (err) { /* ignore */ }
1174
-
1175
- /* ═══ 15 · delegated events ════════════════════════════════════════════ */
1176
- document.addEventListener("click", e => {
1177
- const sri = e.target.closest("[data-sri]");
1178
- if (sri) { srGo(+sri.getAttribute("data-sri")); return; }
1179
- const el = e.target.closest("[data-act]");
1180
- if (!el) return;
1181
- const act = el.getAttribute("data-act"), id = el.getAttribute("data-id");
1182
- if (act === "task") openTask(id);
1183
- else if (act === "mod") { closeDrawer(); selectModule(id); }
1184
- else if (act === "rel") selectRelease(id);
1185
- else if (act === "phase") selectPhase(id);
1186
- else if (act === "clear-sel") clearSel();
1187
- else if (act === "close-drawer") closeDrawer();
1188
- else if (act === "theme") applyTheme(document.body.getAttribute("data-theme") === "paper" ? "night" : "paper");
1189
- else if (act === "go") { const b = document.getElementById(id); if (b) b.scrollIntoView({ block: "start" }); }
1190
- else if (act === "flow-mode") {
1191
- /* scope chips inside band 04 — change the graph in place, never move the page */
1192
- S.flow = id === "sel" && !S.sel ? "focus" : id;
1193
- renderFlow();
1194
- }
1195
- else if (act === "go-flow") {
1196
- /* explicit "take me to the graph" affordances (vitals, attention cards) */
1197
- S.flow = id || "focus";
1198
- renderFlow();
1199
- document.getElementById("flowband").scrollIntoView({ block: "start" });
1200
- }
1201
- else if (act === "flow-done") { S.flowDone = !S.flowDone; renderFlow(); }
1202
- else if (act === "sel-go") selGo();
1203
- else if (act === "show-rels") revealRels();
1204
- else if (act === "idx-filter") { IDX.f = id; renderIndex(); }
1205
- else if (act === "idx-more") {
1206
- const det = document.querySelector('.idx-group[data-mod="' + id + '"]');
1207
- if (det) fillGroup(det, true);
1208
- }
1209
- else if (act === "cone") {
1210
- closeDrawer();
1211
- S.sel = { kind: "task", id };
1212
- S.flow = "sel";
1213
- clearMarks(); renderSelChip(); renderFlow();
1214
- mapDimSet(new Set(flowScopeIds().map(x => (BY[x] || {}).m).filter(Boolean)));
1215
- document.getElementById("flowband").scrollIntoView({ block: "start" });
1216
- }
1217
- });
1218
-
1219
- /* details lazy-fill */
1220
- document.addEventListener("toggle", e => {
1221
- const det = e.target;
1222
- if (det.classList && det.classList.contains("idx-group") && det.open) fillGroup(det);
1223
- if (det.classList && det.classList.contains("commits") && det.open)
1224
- renderCommitRows(det.querySelector(".cbody"), det.getAttribute("data-ck"));
1225
- }, true);
1226
-
1227
- /* hover: tooltips + cross-highlighting */
1228
- document.addEventListener("mouseover", e => {
1229
- const mn = e.target.closest("#map .mnode");
1230
- if (mn) {
1231
- const m = MOD[mn.getAttribute("data-id")];
1232
- if (m) {
1233
- mapFocus(m.id);
1234
- tipShow("<b>" + esc(m.id) + "</b><div class='m'>" + m.total + " tasks — " + (m.done || 0) + " done · " +
1235
- (m.active || 0) + " active · " + (m.draft || 0) + " draft · " + (m.hold || 0) + " hold" +
1236
- ((modBlocked[m.id] || 0) ? "<br><span style='color:var(--bad)'>" + modBlocked[m.id] + " blocked</span>" : "") +
1237
- (m.kind ? "<br><span class='mono'>" + esc(m.kind) + "</span>" : "") + "</div>", e.clientX, e.clientY);
1238
- }
1239
- return;
1240
- }
1241
- const me = e.target.closest("#map .medge");
1242
- if (me) {
1243
- const ei = +me.getAttribute("data-ei"), ed = (D.medges || [])[ei];
1244
- if (ed) tipShow("<b>" + esc(ed.from) + " → " + esc(ed.to) + "</b><div class='m'>" + ed.w +
1245
- " dependency link" + (ed.w > 1 ? "s" : "") + "<br>" +
1246
- ed.pairs.map(p => "<span class='mono'>" + esc(p) + "</span>").join("<br>") + "</div>", e.clientX, e.clientY);
1247
- return;
1248
- }
1249
- const fn = e.target.closest("#flow .fnode");
1250
- if (fn) {
1251
- const t = BY[fn.getAttribute("data-id")];
1252
- if (t) {
1253
- flowCone(t.i, true);
1254
- tipShow("<b>" + esc(t.i) + "</b><div class='m'>" + esc(t.t) + "<br>" +
1255
- esc(stLabel(t.s)) + " · " + esc(t.m) + (t.p ? " · " + esc(t.p) : "") +
1256
- ((t.d || []).length ? "<br>◂ depends on " + t.d.length : "") +
1257
- ((REV[t.i] || []).length ? " · unblocks " + REV[t.i].length + " ▸" : "") + "</div>", e.clientX, e.clientY);
1258
- }
1259
- return;
1260
- }
1261
- const rl = e.target.closest("#roadsvg .rel");
1262
- if (rl) {
1263
- const vs = (rl.getAttribute("data-cluster") || rl.getAttribute("data-id")).split(",");
1264
- const lines = vs.map(v => {
1265
- const r = RELBYV[v];
1266
- return r ? "<span class='mono'>v" + esc(r.v) + "</span> " + esc(r.d || "") + " — " +
1267
- r.notes.features.length + "F · " + r.notes.fixes.length + "X · " + r.notes.improvements.length + "I" +
1268
- (r.cov.unlinked ? " · <span style='color:var(--bad)'>" + r.cov.unlinked + " unlinked</span>" : "") : "";
1269
- });
1270
- tipShow("<b>" + vs.length + " release" + (vs.length > 1 ? "s" : "") + "</b><div class='m'>" +
1271
- lines.join("<br>") + "</div>", e.clientX, e.clientY);
1272
- return;
1273
- }
1274
- const ph = e.target.closest("#roadsvg g.ph");
1275
- if (ph) {
1276
- const p = (D.phases || []).find(x => x.id === ph.getAttribute("data-phase"));
1277
- if (p) tipShow("<b>" + esc(p.label) + "</b><div class='m'>" + p.done + " of " + p.total + " done" +
1278
- (p.start ? "<br>since " + esc(p.start) : "") + (p.lastShip ? " · last ship " + esc(p.lastShip) : "") +
1279
- "<br>click to scope the flow graph</div>", e.clientX, e.clientY);
1280
- }
1281
- });
1282
- document.addEventListener("mouseout", e => {
1283
- if (e.target.closest("#map .mnode") && !e.relatedTarget?.closest?.("#map .mnode")) {
1284
- tipHide();
1285
- mapFocus(S.sel && S.sel.kind === "module" ? S.sel.id : null);
1286
- }
1287
- if (e.target.closest("#flow .fnode") && !e.relatedTarget?.closest?.("#flow .fnode")) {
1288
- tipHide(); flowCone(null, false);
1289
- }
1290
- if (e.target.closest("#map .medge, #roadsvg .rel, #roadsvg g.ph")) tipHide();
1291
- });
1292
- document.addEventListener("mousemove", e => {
1293
- if (tip.classList.contains("on")) {
1294
- const w = tip.offsetWidth, h = tip.offsetHeight;
1295
- let tx = e.clientX + 14, ty = e.clientY + 14;
1296
- if (tx + w > innerWidth - 8) tx = e.clientX - w - 12;
1297
- if (ty + h > innerHeight - 8) ty = e.clientY - h - 12;
1298
- tip.style.left = Math.max(6, tx) + "px";
1299
- tip.style.top = Math.max(6, ty) + "px";
1300
- }
1301
- });
1302
- addEventListener("hashchange", applyHash);
1303
-
1304
- /* ═══ 16 · boot ════════════════════════════════════════════════════════ */
1305
- renderFlow();
1306
- renderIndex();
1307
- applyHash();
1308
- })();