create-yss-spec 1.1.1 → 1.1.3

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 (257) hide show
  1. package/package.json +1 -1
  2. package/template/.agents/skills/high-fidelity-html-prototype/SKILL.md +102 -0
  3. package/template/.codex/skills/data-analytics/.app.json +84 -0
  4. package/template/.codex/skills/data-analytics/.codex-plugin/plugin.json +66 -0
  5. package/template/.codex/skills/data-analytics/.mcp.json +30 -0
  6. package/template/.codex/skills/data-analytics/AGENTS.md +20 -0
  7. package/template/.codex/skills/data-analytics/DEPENDENCIES.MD +27 -0
  8. package/template/.codex/skills/data-analytics/README.md +60 -0
  9. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html +16 -0
  10. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part001 +1 -0
  11. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part002 +1 -0
  12. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part003 +1 -0
  13. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part004 +1 -0
  14. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part005 +1 -0
  15. package/template/.codex/skills/data-analytics/assets/datascience-artifact-widget.html.gz.b64.part006 +1 -0
  16. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html +16 -0
  17. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part001 +1 -0
  18. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part002 +1 -0
  19. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part003 +1 -0
  20. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part004 +1 -0
  21. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part005 +1 -0
  22. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part006 +1 -0
  23. package/template/.codex/skills/data-analytics/assets/datascience-chart-widget.html.gz.b64.part007 +1 -0
  24. package/template/.codex/skills/data-analytics/assets/datascience-small.svg +5 -0
  25. package/template/.codex/skills/data-analytics/assets/datascience-table-widget.html +16 -0
  26. package/template/.codex/skills/data-analytics/assets/datascience-table-widget.html.gz.b64.part001 +1 -0
  27. package/template/.codex/skills/data-analytics/assets/datascience-table-widget.html.gz.b64.part002 +1 -0
  28. package/template/.codex/skills/data-analytics/assets/datascience-table-widget.html.gz.b64.part003 +1 -0
  29. package/template/.codex/skills/data-analytics/assets/datascience-table-widget.html.gz.b64.part004 +1 -0
  30. package/template/.codex/skills/data-analytics/assets/datascience.png +0 -0
  31. package/template/.codex/skills/data-analytics/assets/datascience.svg +10 -0
  32. package/template/.codex/skills/data-analytics/mcp/server.cjs +2963 -0
  33. package/template/.codex/skills/data-analytics/package-lock.json +3048 -0
  34. package/template/.codex/skills/data-analytics/package.json +43 -0
  35. package/template/.codex/skills/data-analytics/scripts/normalize-widget-assets.mjs +75 -0
  36. package/template/.codex/skills/data-analytics/skills/analyze-data-quality/SKILL.md +160 -0
  37. package/template/.codex/skills/data-analytics/skills/analyze-data-quality/agents/openai.yaml +6 -0
  38. package/template/.codex/skills/data-analytics/skills/build-dashboard/SKILL.md +148 -0
  39. package/template/.codex/skills/data-analytics/skills/build-dashboard/agents/openai.yaml +6 -0
  40. package/template/.codex/skills/data-analytics/skills/build-dashboard/specifications/bi-platform-dashboard.md +18 -0
  41. package/template/.codex/skills/data-analytics/skills/build-dashboard/specifications/html-dashboard.md +24 -0
  42. package/template/.codex/skills/data-analytics/skills/build-dashboard/specifications/mcp-artifact-dashboard.md +71 -0
  43. package/template/.codex/skills/data-analytics/skills/build-dashboard/specifications/streamlit-dashboard.md +85 -0
  44. package/template/.codex/skills/data-analytics/skills/build-report/SKILL.md +207 -0
  45. package/template/.codex/skills/data-analytics/skills/build-report/agents/openai.yaml +6 -0
  46. package/template/.codex/skills/data-analytics/skills/build-report/assets/executive-report-shell.html +70 -0
  47. package/template/.codex/skills/data-analytics/skills/build-report/assets/technical-report-shell.html +66 -0
  48. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/SKILL.md +118 -0
  49. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/agents/openai.yaml +6 -0
  50. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/__init__.py +1 -0
  51. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/cli.py +57 -0
  52. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/constants.py +61 -0
  53. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/docx_writer.py +362 -0
  54. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/html_parser.py +829 -0
  55. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/model.py +30 -0
  56. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/plan.py +129 -0
  57. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/quality.py +374 -0
  58. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/rendering.py +613 -0
  59. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/table_utils.py +54 -0
  60. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc/utils.py +16 -0
  61. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-doc/scripts/report_to_google_doc_plan.py +9 -0
  62. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-slides/SKILL.md +78 -0
  63. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-slides/agents/openai.yaml +6 -0
  64. package/template/.codex/skills/data-analytics/skills/build-report/report-to-google-slides/scripts/report_to_google_slides.py +2379 -0
  65. package/template/.codex/skills/data-analytics/skills/build-report/report-to-pdf/SKILL.md +88 -0
  66. package/template/.codex/skills/data-analytics/skills/build-report/report-to-pdf/agents/openai.yaml +6 -0
  67. package/template/.codex/skills/data-analytics/skills/build-report/specifications/executive-report.md +97 -0
  68. package/template/.codex/skills/data-analytics/skills/build-report/specifications/mcp-app-report.md +59 -0
  69. package/template/.codex/skills/data-analytics/skills/build-report/specifications/technical-report.md +75 -0
  70. package/template/.codex/skills/data-analytics/skills/design-kpis/SKILL.md +103 -0
  71. package/template/.codex/skills/data-analytics/skills/design-kpis/agents/openai.yaml +6 -0
  72. package/template/.codex/skills/data-analytics/skills/gather-business-context/SKILL.md +68 -0
  73. package/template/.codex/skills/data-analytics/skills/gather-business-context/agents/openai.yaml +6 -0
  74. package/template/.codex/skills/data-analytics/skills/index/SKILL.md +251 -0
  75. package/template/.codex/skills/data-analytics/skills/index/agents/openai.yaml +6 -0
  76. package/template/.codex/skills/data-analytics/skills/jupyter-notebooks/SKILL.md +131 -0
  77. package/template/.codex/skills/data-analytics/skills/jupyter-notebooks/agents/openai.yaml +6 -0
  78. package/template/.codex/skills/data-analytics/skills/kpi-reporting/SKILL.md +141 -0
  79. package/template/.codex/skills/data-analytics/skills/kpi-reporting/agents/openai.yaml +6 -0
  80. package/template/.codex/skills/data-analytics/skills/kpi-reporting/references/report-templates.md +32 -0
  81. package/template/.codex/skills/data-analytics/skills/market-sizing/SKILL.md +106 -0
  82. package/template/.codex/skills/data-analytics/skills/market-sizing/agents/openai.yaml +6 -0
  83. package/template/.codex/skills/data-analytics/skills/metric-diagnostics/SKILL.md +130 -0
  84. package/template/.codex/skills/data-analytics/skills/metric-diagnostics/agents/openai.yaml +6 -0
  85. package/template/.codex/skills/data-analytics/skills/product-business-analysis/SKILL.md +141 -0
  86. package/template/.codex/skills/data-analytics/skills/product-business-analysis/agents/openai.yaml +6 -0
  87. package/template/.codex/skills/data-analytics/skills/spreadsheets/SKILL.md +178 -0
  88. package/template/.codex/skills/data-analytics/skills/spreadsheets/agents/openai.yaml +9 -0
  89. package/template/.codex/skills/data-analytics/skills/spreadsheets/assets/file-spreadsheet.png +0 -0
  90. package/template/.codex/skills/data-analytics/skills/spreadsheets/charts.md +31 -0
  91. package/template/.codex/skills/data-analytics/skills/spreadsheets/references/artifact_tool_api.md +466 -0
  92. package/template/.codex/skills/data-analytics/skills/spreadsheets/style_guidelines.md +99 -0
  93. package/template/.codex/skills/data-analytics/skills/user-context/SKILL.md +195 -0
  94. package/template/.codex/skills/data-analytics/skills/user-context/agents/openai.yaml +6 -0
  95. package/template/.codex/skills/data-analytics/skills/user-context/plugin-author-config/automation-config.md +26 -0
  96. package/template/.codex/skills/data-analytics/skills/user-context/plugin-author-config/source-category-config.json +51 -0
  97. package/template/.codex/skills/data-analytics/skills/user-context/plugin-author-config/user-context-config.md +66 -0
  98. package/template/.codex/skills/data-analytics/skills/user-context/references/automation.md +69 -0
  99. package/template/.codex/skills/data-analytics/skills/user-context/references/onboarding-examples.md +204 -0
  100. package/template/.codex/skills/data-analytics/skills/user-context/references/onboarding-state-template.json +87 -0
  101. package/template/.codex/skills/data-analytics/skills/user-context/references/onboarding.md +497 -0
  102. package/template/.codex/skills/data-analytics/skills/user-context/references/semantic-layer/connector-playbook.md +74 -0
  103. package/template/.codex/skills/data-analytics/skills/user-context/references/semantic-layer/setup.md +65 -0
  104. package/template/.codex/skills/data-analytics/skills/user-context/references/semantic-layer/skill-template.md +160 -0
  105. package/template/.codex/skills/data-analytics/skills/user-context/references/semantic-layer/source-intake.md +75 -0
  106. package/template/.codex/skills/data-analytics/skills/user-context/references/semantic-layer/weekly-polling-automation.md +96 -0
  107. package/template/.codex/skills/data-analytics/skills/user-context/references/source-category-runtime.md +263 -0
  108. package/template/.codex/skills/data-analytics/skills/user-context/scripts/data_analytics_preflight.py +1460 -0
  109. package/template/.codex/skills/data-analytics/skills/user-context/scripts/init_user_context_state.py +128 -0
  110. package/template/.codex/skills/data-analytics/skills/user-context/scripts/reset_user_context_state.py +101 -0
  111. package/template/.codex/skills/data-analytics/skills/user-context/scripts/validate_user_context_preflight.py +499 -0
  112. package/template/.codex/skills/data-analytics/skills/user-context/tests/test_state_helpers.py +978 -0
  113. package/template/.codex/skills/data-analytics/skills/validate-data/SKILL.md +190 -0
  114. package/template/.codex/skills/data-analytics/skills/validate-data/agents/openai.yaml +6 -0
  115. package/template/.codex/skills/data-analytics/skills/visualize-data/SKILL.md +157 -0
  116. package/template/.codex/skills/data-analytics/skills/visualize-data/agents/openai.yaml +6 -0
  117. package/template/.codex/skills/data-analytics/skills/visualize-data/references/seaborn-templates.md +774 -0
  118. package/template/.codex/skills/data-analytics/src/DESIGN.md +222 -0
  119. package/template/.codex/skills/data-analytics/src/analytics-app/App.tsx +3666 -0
  120. package/template/.codex/skills/data-analytics/src/analytics-app/analytics-layout.test.mjs +136 -0
  121. package/template/.codex/skills/data-analytics/src/analytics-app/charting/ChartFrame.tsx +54 -0
  122. package/template/.codex/skills/data-analytics/src/analytics-app/charting/ChartLegend.tsx +96 -0
  123. package/template/.codex/skills/data-analytics/src/analytics-app/charting/ChartRenderer.tsx +1647 -0
  124. package/template/.codex/skills/data-analytics/src/analytics-app/charting/ChartTooltip.tsx +245 -0
  125. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-app-helpers.tsx +462 -0
  126. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-capabilities.ts +107 -0
  127. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-compatibility.ts +164 -0
  128. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-contract.ts +192 -0
  129. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-theme.ts +203 -0
  130. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-tokens.css +619 -0
  131. package/template/.codex/skills/data-analytics/src/analytics-app/charting/chart-transforms.ts +402 -0
  132. package/template/.codex/skills/data-analytics/src/analytics-app/fonts/SystemSansVariableVF.woff2 +0 -0
  133. package/template/.codex/skills/data-analytics/src/analytics-app/imageExport.ts +370 -0
  134. package/template/.codex/skills/data-analytics/src/analytics-app/layout/AnalyticsLayoutCanvas.tsx +536 -0
  135. package/template/.codex/skills/data-analytics/src/analytics-app/layout/RichMarkdown.tsx +681 -0
  136. package/template/.codex/skills/data-analytics/src/analytics-app/layout/analyticsLayoutCore.ts +164 -0
  137. package/template/.codex/skills/data-analytics/src/analytics-app/main.tsx +21 -0
  138. package/template/.codex/skills/data-analytics/src/analytics-app/scripts/chart_contract.py +472 -0
  139. package/template/.codex/skills/data-analytics/src/analytics-app/scripts/check_analytics_app_runtime_links.py +75 -0
  140. package/template/.codex/skills/data-analytics/src/analytics-app/scripts/design_contract.py +142 -0
  141. package/template/.codex/skills/data-analytics/src/analytics-app/scripts/package_utils.py +142 -0
  142. package/template/.codex/skills/data-analytics/src/analytics-app/scripts/tests/test_package_utils.py +377 -0
  143. package/template/.codex/skills/data-analytics/src/analytics-app/styles.css +3468 -0
  144. package/template/.codex/skills/data-analytics/src/analytics-app/tables/DataTable.d.ts +44 -0
  145. package/template/.codex/skills/data-analytics/src/analytics-app/tables/DataTable.jsx +639 -0
  146. package/template/.codex/skills/data-analytics/src/analytics-app/tables/data-table.css +327 -0
  147. package/template/.codex/skills/data-analytics/src/analytics-app/tokens.css +225 -0
  148. package/template/.codex/skills/data-analytics/src/analytics-app/types.ts +204 -0
  149. package/template/.codex/skills/data-analytics/src/analytics-app-core.md +85 -0
  150. package/template/.codex/skills/data-analytics/src/codex-style-contract.md +30 -0
  151. package/template/.codex/skills/data-analytics/src/datascience-artifact-widget.html +12 -0
  152. package/template/.codex/skills/data-analytics/src/datascience-artifact-widget.jsx +1008 -0
  153. package/template/.codex/skills/data-analytics/src/datascience-chart-widget.css +2117 -0
  154. package/template/.codex/skills/data-analytics/src/datascience-chart-widget.html +122 -0
  155. package/template/.codex/skills/data-analytics/src/datascience-chart-widget.js +3588 -0
  156. package/template/.codex/skills/data-analytics/src/datascience-table-widget.css +483 -0
  157. package/template/.codex/skills/data-analytics/src/datascience-table-widget.html +32 -0
  158. package/template/.codex/skills/data-analytics/src/datascience-table-widget.js +333 -0
  159. package/template/.codex/skills/data-analytics/src/mcp-host.js +153 -0
  160. package/template/.codex/skills/data-analytics/src/recharts-config.js +315 -0
  161. package/template/.codex/skills/data-analytics/src/recharts-renderer.jsx +294 -0
  162. package/template/.codex/skills/data-analytics/src/sql-source-view.css +33 -0
  163. package/template/.codex/skills/data-analytics/src/sql-source-view.js +157 -0
  164. package/template/.codex/skills/data-analytics/src/styles/codex-theme.css +121 -0
  165. package/template/.codex/skills/data-analytics/src/table-renderer.jsx +28 -0
  166. package/template/.codex/skills/data-analytics/tests/chart-transforms.test.mjs +87 -0
  167. package/template/.codex/skills/data-analytics/tests/funnel-smoke.html +105 -0
  168. package/template/.codex/skills/data-analytics/tests/inline-widget-compare.html +233 -0
  169. package/template/.codex/skills/data-analytics/tests/mcp-server.test.mjs +1500 -0
  170. package/template/.codex/skills/data-analytics/tests/native-style-contract.test.mjs +219 -0
  171. package/template/.codex/skills/data-analytics/tests/recharts-config.test.mjs +144 -0
  172. package/template/.codex/skills/data-analytics/tests/recharts-renderer.test.mjs +11 -0
  173. package/template/.codex/skills/data-analytics/tests/rich-markdown.test.mjs +54 -0
  174. package/template/.codex/skills/data-analytics/tests/widget-render-harness.html +321 -0
  175. package/template/.codex/skills/data-analytics/tsconfig.json +19 -0
  176. package/template/.codex/skills/data-analytics/vite.config.ts +46 -0
  177. package/template/.codex/skills/high-fidelity-html-prototype/SKILL.md +102 -0
  178. package/template/.codex/skills/high-fidelity-html-prototype/agents/openai.yaml +5 -0
  179. package/template/.codex/skills/product-design/.app.json +7 -0
  180. package/template/.codex/skills/product-design/.codex-plugin/plugin.json +50 -0
  181. package/template/.codex/skills/product-design/README.md +50 -0
  182. package/template/.codex/skills/product-design/agents/openai.yaml +4 -0
  183. package/template/.codex/skills/product-design/assets/composerIcon.svg +9 -0
  184. package/template/.codex/skills/product-design/assets/logo.png +0 -0
  185. package/template/.codex/skills/product-design/package.json +11 -0
  186. package/template/.codex/skills/product-design/references/browser-order.md +9 -0
  187. package/template/.codex/skills/product-design/references/communication-protocol.md +45 -0
  188. package/template/.codex/skills/product-design/references/critical-overrides.md +58 -0
  189. package/template/.codex/skills/product-design/references/local-prototype-preflight.md +16 -0
  190. package/template/.codex/skills/product-design/scripts/bootstrap-prototype.mjs +147 -0
  191. package/template/.codex/skills/product-design/skills/audit/SKILL.md +159 -0
  192. package/template/.codex/skills/product-design/skills/audit/agents/openai.yaml +6 -0
  193. package/template/.codex/skills/product-design/skills/audit/references/design-audit-framework.md +73 -0
  194. package/template/.codex/skills/product-design/skills/design-qa/SKILL.md +133 -0
  195. package/template/.codex/skills/product-design/skills/design-qa/agents/openai.yaml +6 -0
  196. package/template/.codex/skills/product-design/skills/design-qa/references/qa-rubric.md +61 -0
  197. package/template/.codex/skills/product-design/skills/get-context/SKILL.md +81 -0
  198. package/template/.codex/skills/product-design/skills/get-context/agents/openai.yaml +6 -0
  199. package/template/.codex/skills/product-design/skills/ideate/SKILL.md +179 -0
  200. package/template/.codex/skills/product-design/skills/ideate/agents/openai.yaml +6 -0
  201. package/template/.codex/skills/product-design/skills/image-to-code/SKILL.md +105 -0
  202. package/template/.codex/skills/product-design/skills/image-to-code/agents/openai.yaml +6 -0
  203. package/template/.codex/skills/product-design/skills/index/SKILL.md +126 -0
  204. package/template/.codex/skills/product-design/skills/index/agents/openai.yaml +6 -0
  205. package/template/.codex/skills/product-design/skills/prototype/SKILL.md +128 -0
  206. package/template/.codex/skills/product-design/skills/prototype/agents/openai.yaml +6 -0
  207. package/template/.codex/skills/product-design/skills/prototype/references/existing-codebase-edits.md +18 -0
  208. package/template/.codex/skills/product-design/skills/research/SKILL.md +92 -0
  209. package/template/.codex/skills/product-design/skills/research/agents/openai.yaml +6 -0
  210. package/template/.codex/skills/product-design/skills/share/SKILL.md +41 -0
  211. package/template/.codex/skills/product-design/skills/share/agents/openai.yaml +6 -0
  212. package/template/.codex/skills/product-design/skills/url-to-code/SKILL.md +124 -0
  213. package/template/.codex/skills/product-design/skills/url-to-code/agents/openai.yaml +6 -0
  214. package/template/.codex/skills/product-design/skills/user-context/SKILL.md +147 -0
  215. package/template/.codex/skills/product-design/skills/user-context/agents/openai.yaml +6 -0
  216. package/template/.codex/skills/product-design/skills/user-context/plugin-author-config/user-context-template.md +63 -0
  217. package/template/.codex/skills/product-design/skills/user-context/references/onboarding.md +66 -0
  218. package/template/.codex/skills/product-design/skills/user-context/scripts/init_user_context.py +86 -0
  219. package/template/.codex/skills/product-design/skills/user-context/scripts/user_context_preflight.py +204 -0
  220. package/template/.codex/skills/product-design/templates/prototype/AGENTS.md +7 -0
  221. package/template/.codex/skills/product-design/templates/prototype/index.html +12 -0
  222. package/template/.codex/skills/product-design/templates/prototype/package.json +18 -0
  223. package/template/.codex/skills/product-design/templates/prototype/src/App.jsx +5 -0
  224. package/template/.codex/skills/product-design/templates/prototype/src/main.jsx +10 -0
  225. package/template/.codex/skills/product-design/templates/prototype/src/styles.css +13 -0
  226. package/template/.codex/skills/product-design/templates/prototype/vite.config.mjs +14 -0
  227. package/template/.codex/skills/product-design-prototype/SKILL.md +2 -4
  228. package/template/.codex/skills/prototype-review/SKILL.md +7 -7
  229. package/template/.codex/skills/yss-design-system/SKILL.md +4 -2
  230. package/template/.codex/skills/yss-product-lifecycle/SKILL.md +10 -7
  231. package/template/.codex/skills/yss-product-lifecycle/references/artifact-checklist.md +1 -0
  232. package/template/.codex/skills/yss-product-lifecycle/references/stage-routing.md +1 -1
  233. package/template/.codex/skills/yss-ui/SKILL.md +4 -10
  234. package/template/.codex/skills/yss-ui/references/quick-recipes.md +1 -1
  235. package/template/AGENTS.md +9 -3
  236. package/template/CONTEXT.md +1 -0
  237. package/template/docs/adr/adr-product-design-prototype-entrypoint.md +54 -0
  238. package/template/docs/api/templates/openapi-draft-review-checklist.md +5 -1
  239. package/template/docs/design/README.md +13 -6
  240. package/template/docs/design/prototypes/.gitkeep +1 -0
  241. package/template/docs/design/templates/interaction-spec-template.md +8 -3
  242. package/template/docs/design/templates/product-overview-design-template.md +34 -5
  243. package/template/docs/design/templates/prototype-confirmation-template.md +57 -0
  244. package/template/docs/design/templates/prototype-review-checklist.md +7 -3
  245. package/template/docs/design/templates/state-matrix-template.md +1 -1
  246. package/template/docs/process/harness-executive-blueprint.md +6 -1
  247. package/template/docs/process/harness-process-tailoring.md +3 -3
  248. package/template/docs/process/harness-work-unit-map.md +1 -1
  249. package/template/docs/process/lifecycle-artifact-map.md +7 -5
  250. package/template/docs/process/product-prototype-workflow.md +82 -0
  251. package/template/docs/templates/requirement-freeze-template.md +4 -3
  252. package/template/docs/user-guide/product-lifecycle-workflow.md +2 -2
  253. package/template/docs/user-guide/product-rd-lifecycle-best-practices.md +54 -24
  254. package/template/.codex/skills/component-story-prototype/SKILL.md +0 -55
  255. package/template/.codex/skills/component-story-prototype/agents/openai.yaml +0 -4
  256. package/template/.codex/skills/mock-api-prototype/SKILL.md +0 -48
  257. package/template/.codex/skills/mock-api-prototype/agents/openai.yaml +0 -4
@@ -0,0 +1,44 @@
1
+ import type { ReactElement } from "react";
2
+
3
+ export type SharedTableColumn = {
4
+ align?: "center" | "left" | "right";
5
+ field?: string;
6
+ format?: "compact" | "currency" | "number" | "percent" | string;
7
+ key?: string;
8
+ label?: string;
9
+ minWidth?: number;
10
+ movement?: boolean;
11
+ name?: string;
12
+ role?: "movement" | "value" | string;
13
+ semantic?: "movement" | "value" | string;
14
+ type?: "currency" | "date" | "number" | "percent" | "text" | string;
15
+ unit?: string;
16
+ };
17
+
18
+ export type SharedDataTableColumnWidthOptions = {
19
+ persist?: boolean;
20
+ };
21
+
22
+ export type SharedDataTableProps = {
23
+ columnWidths?: Record<string, number>;
24
+ columns?: SharedTableColumn[];
25
+ density?: "dense" | "spacious";
26
+ emptyLabel?: string;
27
+ isFullscreen?: boolean;
28
+ maxRows?: number;
29
+ onColumnWidthsChange?: (
30
+ nextWidths: Record<string, number>,
31
+ options?: SharedDataTableColumnWidthOptions
32
+ ) => void;
33
+ pageSize?: number;
34
+ rows?: Array<Record<string, unknown>>;
35
+ showCount?: boolean;
36
+ };
37
+
38
+ export function DataTable(props: SharedDataTableProps): ReactElement;
39
+
40
+ export function tableRowsToMarkdown(args: {
41
+ columns?: SharedTableColumn[];
42
+ maxRows?: number;
43
+ rows?: Array<Record<string, unknown>>;
44
+ }): string;
@@ -0,0 +1,639 @@
1
+ import React from "react";
2
+ import { ArrowDown, ArrowUp, ChevronLeft, ChevronRight } from "lucide-react";
3
+
4
+ const DEFAULT_PAGE_SIZE = 15;
5
+ const TABLE_COLUMN_DEFAULT_WIDTH = 160;
6
+ const TABLE_COLUMN_KEYBOARD_STEP = 24;
7
+ const TABLE_COLUMN_MAX_WIDTH = 720;
8
+ const TABLE_COLUMN_MIN_WIDTH = 88;
9
+ const TABLE_COLUMN_RANK_MIN_WIDTH = 72;
10
+ const TABLE_COLUMN_SAMPLE_SIZE = 25;
11
+ const COMPACT_VALUE_THRESHOLD = 100_000;
12
+
13
+ function text(value) {
14
+ return value == null ? "" : String(value);
15
+ }
16
+
17
+ function numeric(value) {
18
+ if (typeof value === "number" && Number.isFinite(value)) return value;
19
+ if (typeof value === "string") {
20
+ const parsed = Number(value.replace(/[,%]/g, ""));
21
+ return Number.isFinite(parsed) ? parsed : null;
22
+ }
23
+ return null;
24
+ }
25
+
26
+ function dateParts(value) {
27
+ if (typeof value !== "string") return null;
28
+ const match = value.trim().match(/^(\d{4})-(\d{2})-(\d{2})(?:$|[T\s])/);
29
+ if (!match) return null;
30
+ const [, year, month, day] = match;
31
+ return {
32
+ day: Number(day),
33
+ month: Number(month),
34
+ year: Number(year),
35
+ };
36
+ }
37
+
38
+ function isDateLikeValue(value) {
39
+ if (value instanceof Date) return !Number.isNaN(value.getTime());
40
+ const parts = dateParts(value);
41
+ if (!parts) return false;
42
+ const date = new Date(parts.year, parts.month - 1, parts.day);
43
+ return (
44
+ date.getFullYear() === parts.year &&
45
+ date.getMonth() === parts.month - 1 &&
46
+ date.getDate() === parts.day
47
+ );
48
+ }
49
+
50
+ function formatTableDate(value) {
51
+ if (value instanceof Date && !Number.isNaN(value.getTime())) {
52
+ return new Intl.DateTimeFormat(undefined, {
53
+ day: "numeric",
54
+ month: "short",
55
+ year: "numeric",
56
+ }).format(value);
57
+ }
58
+ const parts = dateParts(value);
59
+ if (!parts) return text(value);
60
+ return new Intl.DateTimeFormat(undefined, {
61
+ day: "numeric",
62
+ month: "short",
63
+ year: "numeric",
64
+ }).format(new Date(parts.year, parts.month - 1, parts.day));
65
+ }
66
+
67
+ function titleCaseKey(key) {
68
+ return text(key)
69
+ .replaceAll("_", " ")
70
+ .replace(/\b\w/g, (letter) => letter.toUpperCase());
71
+ }
72
+
73
+ function isUsdUnit(unit) {
74
+ return unit === "$" || text(unit).toUpperCase() === "USD";
75
+ }
76
+
77
+ function unitScaleSuffix(unit) {
78
+ const normalized = text(unit).toLowerCase();
79
+ if (normalized === "usd millions" || normalized === "usd million") return "M";
80
+ if (normalized === "usd billions" || normalized === "usd billion") return "B";
81
+ if (normalized === "usd thousands" || normalized === "usd thousand") return "K";
82
+ if (/^\$[kmbt]$/i.test(text(unit))) return text(unit).slice(1).toUpperCase();
83
+ return null;
84
+ }
85
+
86
+ function compactDigits(value) {
87
+ const absolute = Math.abs(value);
88
+ if (absolute >= COMPACT_VALUE_THRESHOLD) return 1;
89
+ return 2;
90
+ }
91
+
92
+ function formatNumber(value, { compact = false } = {}) {
93
+ const absolute = Math.abs(value);
94
+ return new Intl.NumberFormat(undefined, {
95
+ maximumFractionDigits: compactDigits(value),
96
+ notation: compact && absolute >= COMPACT_VALUE_THRESHOLD ? "compact" : "standard",
97
+ }).format(value);
98
+ }
99
+
100
+ function formatCurrency(value) {
101
+ const absolute = Math.abs(value);
102
+ return new Intl.NumberFormat(undefined, {
103
+ style: "currency",
104
+ currency: "USD",
105
+ maximumFractionDigits: compactDigits(value),
106
+ notation: absolute >= COMPACT_VALUE_THRESHOLD ? "compact" : "standard",
107
+ }).format(value);
108
+ }
109
+
110
+ function signedLabel(value, label, signed = false) {
111
+ if (signed && value > 0) return `+${label}`;
112
+ return label;
113
+ }
114
+
115
+ function formatScaledCurrency(value, scaleSuffix, signed) {
116
+ const absolute = Math.abs(value);
117
+ const sign = value < 0 ? "-" : signed && value > 0 ? "+" : "";
118
+ return `${sign}$${formatNumber(absolute)}${scaleSuffix}`;
119
+ }
120
+
121
+ export function inferTableColumns(rows) {
122
+ const seen = new Set();
123
+ const columns = [];
124
+ for (const row of Array.isArray(rows) ? rows : []) {
125
+ if (!row || typeof row !== "object") continue;
126
+ for (const key of Object.keys(row)) {
127
+ if (seen.has(key)) continue;
128
+ seen.add(key);
129
+ columns.push({ key, label: titleCaseKey(key) });
130
+ }
131
+ }
132
+ return columns;
133
+ }
134
+
135
+ export function normalizeTableColumns(columns, rows) {
136
+ const normalized = [];
137
+ const seen = new Set();
138
+ for (const column of Array.isArray(columns) ? columns : []) {
139
+ if (!column || typeof column !== "object") continue;
140
+ const key = text(column.key || column.field);
141
+ if (!key || seen.has(key)) continue;
142
+ seen.add(key);
143
+ normalized.push({
144
+ ...column,
145
+ field: column.field || key,
146
+ key,
147
+ label: column.label || column.name || titleCaseKey(key),
148
+ });
149
+ }
150
+ return normalized.length ? normalized : inferTableColumns(rows);
151
+ }
152
+
153
+ export function columnType(column, rows) {
154
+ if (column?.type) return column.type;
155
+ if (column?.format === "currency") return "currency";
156
+ if (column?.format === "percent") return "percent";
157
+ if (column?.format) return "number";
158
+ const key = column?.key || column?.field;
159
+ const values = (Array.isArray(rows) ? rows : [])
160
+ .map((row) => row && row[key])
161
+ .filter((value) => value != null);
162
+ if (values.length && values.every(isDateLikeValue)) return "date";
163
+ if (values.length && values.every((value) => typeof value === "number")) return "number";
164
+ if (values.length && values.every((value) => numeric(value) != null)) return "number";
165
+ return "text";
166
+ }
167
+
168
+ export function formatTableValue(value, type, unit, options = {}) {
169
+ const signed = Boolean(options.signed);
170
+ if (value == null) return "";
171
+ if (type === "date") return formatTableDate(value);
172
+ const numericValue = numeric(value);
173
+ if (type === "percent" && numericValue != null) {
174
+ const label = new Intl.NumberFormat(undefined, {
175
+ style: "percent",
176
+ maximumFractionDigits: Math.abs(numericValue) < 0.1 ? 1 : 0,
177
+ }).format(numericValue);
178
+ return signedLabel(numericValue, label, signed);
179
+ }
180
+ const scaleSuffix = unitScaleSuffix(unit);
181
+ if (scaleSuffix && numericValue != null) {
182
+ return formatScaledCurrency(numericValue, scaleSuffix, signed);
183
+ }
184
+ if (type === "currency" && numericValue != null) {
185
+ return signedLabel(numericValue, formatCurrency(numericValue), signed);
186
+ }
187
+ if (type === "number" && numericValue != null) {
188
+ if (isUsdUnit(unit)) return signedLabel(numericValue, formatCurrency(numericValue), signed);
189
+ const suffix = unit ? ` ${unit}` : "";
190
+ return signedLabel(numericValue, `${formatNumber(numericValue, { compact: true })}${suffix}`, signed);
191
+ }
192
+ return text(value);
193
+ }
194
+
195
+ function isMovementColumn(column) {
196
+ return column?.movement === true || column?.semantic === "movement" || column?.role === "movement";
197
+ }
198
+
199
+ function movementDirection(column, value) {
200
+ if (!isMovementColumn(column)) return null;
201
+ if (typeof value !== "string") {
202
+ const numericValue = numeric(value);
203
+ if (numericValue > 0) return "positive";
204
+ if (numericValue < 0) return "negative";
205
+ return null;
206
+ }
207
+ const trimmed = value.trim();
208
+ if (!trimmed) return null;
209
+ if (/^[+↑]/.test(trimmed)) return "positive";
210
+ if (/^[-−↓]/.test(trimmed)) return "negative";
211
+ const numericValue = numeric(trimmed);
212
+ if (numericValue > 0) return "positive";
213
+ if (numericValue < 0) return "negative";
214
+ return null;
215
+ }
216
+
217
+ function movementClassName(column, value) {
218
+ const direction = movementDirection(column, value);
219
+ if (!direction || direction === "neutral") return "";
220
+ return `table-cell-movement table-cell-movement-${direction}`;
221
+ }
222
+
223
+ function columnLooksLikeRank(column) {
224
+ const descriptor = text(column?.key || column?.field || column?.label || column?.name).trim();
225
+ return /^(#|rank|ranking|position)$/i.test(descriptor);
226
+ }
227
+
228
+ function columnMinWidth(column) {
229
+ if (column?.minWidth != null) {
230
+ const parsed = Number(column.minWidth);
231
+ if (Number.isFinite(parsed)) return clamp(parsed, TABLE_COLUMN_RANK_MIN_WIDTH, TABLE_COLUMN_MAX_WIDTH);
232
+ }
233
+ return columnLooksLikeRank(column) ? TABLE_COLUMN_RANK_MIN_WIDTH : TABLE_COLUMN_MIN_WIDTH;
234
+ }
235
+
236
+ function defaultColumnAlign(column, type) {
237
+ if (column.align) return column.align;
238
+ if (columnLooksLikeRank(column)) return "center";
239
+ if (type === "number" || type === "percent" || type === "currency") return "right";
240
+ return "left";
241
+ }
242
+
243
+ function columnGrowWeight(column, rows) {
244
+ if (columnLooksLikeRank(column)) return 0;
245
+ const type = columnType(column, rows);
246
+ if (type === "text" || type === "date") return 1.4;
247
+ return 1;
248
+ }
249
+
250
+ function sortRows(rows, columns, sortState) {
251
+ if (!sortState?.key) return rows;
252
+ const column = columns.find((item) => item.key === sortState.key) || {};
253
+ const type = columnType(column, rows);
254
+ return [...rows].sort((a, b) => {
255
+ const av = a ? a[sortState.key] : null;
256
+ const bv = b ? b[sortState.key] : null;
257
+ let result = 0;
258
+ if (type === "number" || type === "percent" || type === "currency") {
259
+ const aNumber = numeric(av);
260
+ const bNumber = numeric(bv);
261
+ if (aNumber == null && bNumber == null) result = 0;
262
+ else if (aNumber == null) result = 1;
263
+ else if (bNumber == null) result = -1;
264
+ else result = aNumber - bNumber;
265
+ } else {
266
+ result = text(av).localeCompare(text(bv), undefined, { numeric: true });
267
+ }
268
+ return sortState.direction === "desc" ? -result : result;
269
+ });
270
+ }
271
+
272
+ function clamp(value, min, max) {
273
+ return Math.min(max, Math.max(min, value));
274
+ }
275
+
276
+ function estimateTableColumnWidths(columns, rows) {
277
+ return Object.fromEntries(
278
+ columns.map((column) => {
279
+ const key = column.key;
280
+ const type = columnType(column, rows);
281
+ const sampleValues = rows
282
+ .slice(0, TABLE_COLUMN_SAMPLE_SIZE)
283
+ .map((row) => formatTableValue(row && row[key], type, column.unit));
284
+ const longest = Math.max(text(column.label).length, ...sampleValues.map((value) => text(value).length));
285
+ const width = clamp(Math.ceil(longest * 7.5) + 42, columnMinWidth(column), TABLE_COLUMN_MAX_WIDTH);
286
+ return [key, width];
287
+ }),
288
+ );
289
+ }
290
+
291
+ function stretchTableColumnWidths(columns, rows, baseWidths, targetWidth) {
292
+ const baseWidth = columns.reduce(
293
+ (total, column) => total + (baseWidths[column.key] ?? TABLE_COLUMN_DEFAULT_WIDTH),
294
+ 0,
295
+ );
296
+ const availableWidth = Number.isFinite(targetWidth) ? Math.floor(targetWidth) : 0;
297
+ if (availableWidth <= baseWidth) return baseWidths;
298
+
299
+ const weights = columns.map((column) => columnGrowWeight(column, rows));
300
+ const totalWeight = weights.reduce((total, weight) => total + weight, 0);
301
+ if (!totalWeight) return baseWidths;
302
+
303
+ const extraWidth = availableWidth - baseWidth;
304
+ return Object.fromEntries(
305
+ columns.map((column, index) => [
306
+ column.key,
307
+ (baseWidths[column.key] ?? TABLE_COLUMN_DEFAULT_WIDTH) + (extraWidth * weights[index]) / totalWeight,
308
+ ]),
309
+ );
310
+ }
311
+
312
+ function SortIcon({ direction }) {
313
+ const Icon = direction === "desc" ? ArrowDown : ArrowUp;
314
+ return <Icon aria-hidden="true" className="table-sort-icon" size={12} strokeWidth={2} />;
315
+ }
316
+
317
+ function markdownCell(value) {
318
+ return text(value)
319
+ .replace(/\r?\n/g, "<br>")
320
+ .replace(/\|/g, "\\|")
321
+ .trim();
322
+ }
323
+
324
+ export function tableRowsToMarkdown({ columns, maxRows = 2000, rows }) {
325
+ const safeRows = Array.isArray(rows) ? rows : [];
326
+ const normalizedColumns = normalizeTableColumns(columns, safeRows);
327
+ if (!normalizedColumns.length) return "";
328
+ const header = `| ${normalizedColumns.map((column) => markdownCell(column.label)).join(" | ")} |`;
329
+ const divider = `| ${normalizedColumns.map(() => "---").join(" | ")} |`;
330
+ const body = safeRows.slice(0, maxRows).map((row) => {
331
+ const cells = normalizedColumns.map((column) => {
332
+ const type = columnType(column, safeRows);
333
+ return markdownCell(
334
+ formatTableValue(row && row[column.key], type, column.unit, {
335
+ signed: isMovementColumn(column),
336
+ }),
337
+ );
338
+ });
339
+ return `| ${cells.join(" | ")} |`;
340
+ });
341
+ return [header, divider, ...body].join("\n");
342
+ }
343
+
344
+ export function DataTable({
345
+ columnWidths,
346
+ columns,
347
+ density = "dense",
348
+ emptyLabel = "No rows to render.",
349
+ isFullscreen = false,
350
+ maxRows = 2000,
351
+ onColumnWidthsChange,
352
+ pageSize = DEFAULT_PAGE_SIZE,
353
+ rows,
354
+ showCount = true,
355
+ }) {
356
+ const safeRows = Array.isArray(rows) ? rows : [];
357
+ const normalizedColumns = React.useMemo(
358
+ () => normalizeTableColumns(columns, safeRows),
359
+ [columns, safeRows],
360
+ );
361
+ const [sortState, setSortState] = React.useState(null);
362
+ const [page, setPage] = React.useState(0);
363
+ const [internalColumnWidths, setInternalColumnWidths] = React.useState({});
364
+ const [tableWrapWidth, setTableWrapWidth] = React.useState(0);
365
+ const [isColumnResizing, setIsColumnResizing] = React.useState(false);
366
+ const tableWrapRef = React.useRef(null);
367
+ const effectiveColumnWidths = columnWidths || internalColumnWidths;
368
+
369
+ React.useEffect(() => {
370
+ setPage(0);
371
+ if (!columnWidths) setInternalColumnWidths({});
372
+ }, [rows, columns]);
373
+
374
+ React.useLayoutEffect(() => {
375
+ if (tableWrapRef.current) tableWrapRef.current.scrollLeft = 0;
376
+ }, [rows, columns]);
377
+
378
+ React.useLayoutEffect(() => {
379
+ const node = tableWrapRef.current;
380
+ if (!node) return undefined;
381
+
382
+ const measure = () => {
383
+ const nextWidth = Math.floor(node.getBoundingClientRect().width || 0);
384
+ setTableWrapWidth((current) => (current === nextWidth ? current : nextWidth));
385
+ };
386
+
387
+ measure();
388
+ const observer = typeof ResizeObserver === "function" ? new ResizeObserver(measure) : null;
389
+ observer?.observe(node);
390
+ window.addEventListener("resize", measure);
391
+ return () => {
392
+ observer?.disconnect();
393
+ window.removeEventListener("resize", measure);
394
+ };
395
+ }, []);
396
+
397
+ const sortedRows = React.useMemo(
398
+ () => sortRows(safeRows, normalizedColumns, sortState).slice(0, maxRows),
399
+ [maxRows, normalizedColumns, safeRows, sortState],
400
+ );
401
+ const effectivePageSize = isFullscreen ? Math.max(1, sortedRows.length) : pageSize;
402
+ const totalPages = Math.max(1, Math.ceil(sortedRows.length / effectivePageSize));
403
+ const currentPage = Math.min(page, totalPages - 1);
404
+ const visibleRows = sortedRows.slice(
405
+ currentPage * effectivePageSize,
406
+ currentPage * effectivePageSize + effectivePageSize,
407
+ );
408
+ const isTruncated = safeRows.length > sortedRows.length;
409
+ const shouldShowPagination = safeRows.length > 0 && !isFullscreen && totalPages > 1;
410
+ const shouldShowCount = showCount && (shouldShowPagination || isTruncated);
411
+ const resultCountLabel = safeRows.length > sortedRows.length
412
+ ? `${sortedRows.length.toLocaleString()} of ${safeRows.length.toLocaleString()} results`
413
+ : `${safeRows.length.toLocaleString()} ${safeRows.length === 1 ? "result" : "results"}`;
414
+ const activeColumnWidths = React.useMemo(() => {
415
+ const estimatedWidths = estimateTableColumnWidths(normalizedColumns, safeRows);
416
+ return Object.fromEntries(
417
+ normalizedColumns.map((column) => {
418
+ const key = column.key;
419
+ return [
420
+ key,
421
+ clamp(
422
+ Math.round(effectiveColumnWidths[key] ?? estimatedWidths[key] ?? TABLE_COLUMN_DEFAULT_WIDTH),
423
+ columnMinWidth(column),
424
+ TABLE_COLUMN_MAX_WIDTH,
425
+ ),
426
+ ];
427
+ }),
428
+ );
429
+ }, [effectiveColumnWidths, normalizedColumns, safeRows]);
430
+ const renderedColumnWidths = React.useMemo(
431
+ () => stretchTableColumnWidths(normalizedColumns, safeRows, activeColumnWidths, tableWrapWidth),
432
+ [activeColumnWidths, normalizedColumns, safeRows, tableWrapWidth],
433
+ );
434
+ const tablePixelWidth = normalizedColumns.reduce(
435
+ (total, column) => total + (activeColumnWidths[column.key] ?? TABLE_COLUMN_DEFAULT_WIDTH),
436
+ 0,
437
+ );
438
+ const renderedTableWidth = normalizedColumns.reduce(
439
+ (total, column) => total + (renderedColumnWidths[column.key] ?? activeColumnWidths[column.key] ?? TABLE_COLUMN_DEFAULT_WIDTH),
440
+ 0,
441
+ );
442
+
443
+ function toggleSort(key) {
444
+ setSortState((current) => {
445
+ if (current?.key === key) {
446
+ return { key, direction: current.direction === "asc" ? "desc" : "asc" };
447
+ }
448
+ return { key, direction: "asc" };
449
+ });
450
+ setPage(0);
451
+ }
452
+
453
+ function resizeColumnBy(key, delta) {
454
+ const currentWidth = renderedColumnWidths[key] ?? activeColumnWidths[key] ?? TABLE_COLUMN_DEFAULT_WIDTH;
455
+ const column = normalizedColumns.find((item) => item.key === key);
456
+ commitColumnWidths({
457
+ ...activeColumnWidths,
458
+ ...effectiveColumnWidths,
459
+ [key]: clamp(Math.round(currentWidth + delta), columnMinWidth(column), TABLE_COLUMN_MAX_WIDTH),
460
+ }, { persist: true });
461
+ }
462
+
463
+ function startColumnResize(event, key) {
464
+ event.preventDefault();
465
+ event.stopPropagation();
466
+ const baseWidths = { ...activeColumnWidths, ...renderedColumnWidths, ...effectiveColumnWidths };
467
+ const startWidth = baseWidths[key] ?? TABLE_COLUMN_DEFAULT_WIDTH;
468
+ const startX = event.clientX;
469
+ const column = normalizedColumns.find((item) => item.key === key);
470
+ let latestWidths = baseWidths;
471
+ commitColumnWidths(baseWidths, { persist: false });
472
+ setIsColumnResizing(true);
473
+ document.body.style.cursor = "col-resize";
474
+ document.body.style.userSelect = "none";
475
+
476
+ function handlePointerMove(moveEvent) {
477
+ latestWidths = {
478
+ ...baseWidths,
479
+ [key]: clamp(
480
+ Math.round(startWidth + moveEvent.clientX - startX),
481
+ columnMinWidth(column),
482
+ TABLE_COLUMN_MAX_WIDTH,
483
+ ),
484
+ };
485
+ commitColumnWidths(latestWidths, { persist: false });
486
+ }
487
+
488
+ function finishResize() {
489
+ window.removeEventListener("pointermove", handlePointerMove);
490
+ window.removeEventListener("pointerup", finishResize);
491
+ window.removeEventListener("pointercancel", finishResize);
492
+ document.body.style.cursor = "";
493
+ document.body.style.userSelect = "";
494
+ setIsColumnResizing(false);
495
+ commitColumnWidths(latestWidths, { persist: true });
496
+ }
497
+
498
+ window.addEventListener("pointermove", handlePointerMove);
499
+ window.addEventListener("pointerup", finishResize);
500
+ window.addEventListener("pointercancel", finishResize);
501
+ }
502
+
503
+ function commitColumnWidths(nextWidths, options) {
504
+ if (onColumnWidthsChange) {
505
+ onColumnWidthsChange(nextWidths, options);
506
+ return;
507
+ }
508
+ setInternalColumnWidths(nextWidths);
509
+ }
510
+
511
+ if (!safeRows.length || !normalizedColumns.length) {
512
+ return <div className="empty-state">{emptyLabel}</div>;
513
+ }
514
+
515
+ return (
516
+ <div className="shared-table-shell">
517
+ <div className={`table-wrap table-density-${density} ${isFullscreen ? "fullscreen" : ""}`.trim()} ref={tableWrapRef}>
518
+ <table
519
+ className={`data-table data-table-${density} data-table-resizable ${isColumnResizing ? "is-column-resizing" : ""}`}
520
+ style={{
521
+ minWidth: `${tablePixelWidth}px`,
522
+ tableLayout: "fixed",
523
+ width: `${renderedTableWidth}px`,
524
+ }}
525
+ >
526
+ <colgroup>
527
+ {normalizedColumns.map((column) => (
528
+ <col key={column.key} style={{ width: `${renderedColumnWidths[column.key] ?? activeColumnWidths[column.key] ?? TABLE_COLUMN_DEFAULT_WIDTH}px` }} />
529
+ ))}
530
+ </colgroup>
531
+ <thead>
532
+ <tr>
533
+ {normalizedColumns.map((column) => {
534
+ const key = column.key;
535
+ const active = sortState?.key === key;
536
+ const type = columnType(column, safeRows);
537
+ const align = defaultColumnAlign(column, type);
538
+ return (
539
+ <th
540
+ aria-sort={active ? (sortState.direction === "asc" ? "ascending" : "descending") : "none"}
541
+ className={`${align === "right" ? "numeric" : align === "center" ? "center" : "left"} ${active ? "is-sorted" : ""}`}
542
+ key={key}
543
+ style={{ width: `${renderedColumnWidths[key] ?? activeColumnWidths[key] ?? TABLE_COLUMN_DEFAULT_WIDTH}px` }}
544
+ >
545
+ <button
546
+ aria-label={
547
+ active
548
+ ? `Sort ${column.label} ${sortState.direction === "asc" ? "descending" : "ascending"}`
549
+ : `Sort ${column.label} ascending`
550
+ }
551
+ className="table-sort-button"
552
+ onClick={() => toggleSort(key)}
553
+ type="button"
554
+ >
555
+ <span>{column.label}</span>
556
+ {active ? <SortIcon direction={sortState.direction} /> : null}
557
+ </button>
558
+ <button
559
+ aria-label={`Resize ${column.label} column`}
560
+ className="table-column-resize-handle"
561
+ onKeyDown={(event) => {
562
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
563
+ event.preventDefault();
564
+ event.stopPropagation();
565
+ resizeColumnBy(key, event.key === "ArrowRight" ? TABLE_COLUMN_KEYBOARD_STEP : -TABLE_COLUMN_KEYBOARD_STEP);
566
+ }}
567
+ onPointerDown={(event) => startColumnResize(event, key)}
568
+ title={`Resize ${column.label} column`}
569
+ type="button"
570
+ />
571
+ </th>
572
+ );
573
+ })}
574
+ </tr>
575
+ </thead>
576
+ <tbody>
577
+ {visibleRows.map((row, rowIndex) => (
578
+ <tr key={rowIndex}>
579
+ {normalizedColumns.map((column) => {
580
+ const type = columnType(column, safeRows);
581
+ const align = defaultColumnAlign(column, type);
582
+ const value = row ? row[column.key] : null;
583
+ const className = [
584
+ align === "right" ? "table-cell-number" : "",
585
+ align === "center" ? "center" : "",
586
+ type === "date" ? "table-cell-date" : "",
587
+ movementClassName(column, value),
588
+ ].filter(Boolean).join(" ") || undefined;
589
+ return (
590
+ <td
591
+ className={className}
592
+ key={column.key}
593
+ >
594
+ {formatTableValue(value, type, column.unit, {
595
+ signed: isMovementColumn(column),
596
+ })}
597
+ </td>
598
+ );
599
+ })}
600
+ </tr>
601
+ ))}
602
+ </tbody>
603
+ </table>
604
+ </div>
605
+ {shouldShowCount || shouldShowPagination ? (
606
+ <div className="shared-table-footer table-pagination">
607
+ {shouldShowCount ? (
608
+ <span className="table-result-count">{resultCountLabel}</span>
609
+ ) : null}
610
+ {shouldShowPagination ? (
611
+ <div className="table-page-control">
612
+ <span>Page {currentPage + 1} of {totalPages}</span>
613
+ <div className="table-page-buttons">
614
+ <button
615
+ aria-label="Previous page"
616
+ className="table-arrow-button"
617
+ disabled={currentPage === 0}
618
+ onClick={() => setPage(Math.max(0, currentPage - 1))}
619
+ type="button"
620
+ >
621
+ <ChevronLeft aria-hidden="true" size={14} strokeWidth={2} />
622
+ </button>
623
+ <button
624
+ aria-label="Next page"
625
+ className="table-arrow-button"
626
+ disabled={currentPage >= totalPages - 1}
627
+ onClick={() => setPage(Math.min(totalPages - 1, currentPage + 1))}
628
+ type="button"
629
+ >
630
+ <ChevronRight aria-hidden="true" size={14} strokeWidth={2} />
631
+ </button>
632
+ </div>
633
+ </div>
634
+ ) : null}
635
+ </div>
636
+ ) : null}
637
+ </div>
638
+ );
639
+ }