@panphora/bevel 0.1.0

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 (539) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +226 -0
  3. package/bevel-fonts.css +16 -0
  4. package/bevel.css +3075 -0
  5. package/bevel.js +2126 -0
  6. package/dist/react/components/activity-feed.d.ts +89 -0
  7. package/dist/react/components/activity-feed.js +54 -0
  8. package/dist/react/components/alert-dialog.d.ts +14 -0
  9. package/dist/react/components/alert-dialog.js +19 -0
  10. package/dist/react/components/app-shell.d.ts +98 -0
  11. package/dist/react/components/app-shell.js +86 -0
  12. package/dist/react/components/app-toolbar.d.ts +19 -0
  13. package/dist/react/components/app-toolbar.js +22 -0
  14. package/dist/react/components/avatar-group.d.ts +67 -0
  15. package/dist/react/components/avatar-group.js +49 -0
  16. package/dist/react/components/badge-and-pill.d.ts +34 -0
  17. package/dist/react/components/badge-and-pill.js +12 -0
  18. package/dist/react/components/breadcrumbs.d.ts +56 -0
  19. package/dist/react/components/breadcrumbs.js +44 -0
  20. package/dist/react/components/bulk-bar.d.ts +22 -0
  21. package/dist/react/components/bulk-bar.js +59 -0
  22. package/dist/react/components/button.d.ts +15 -0
  23. package/dist/react/components/button.js +20 -0
  24. package/dist/react/components/callout-and-hint.d.ts +17 -0
  25. package/dist/react/components/callout-and-hint.js +8 -0
  26. package/dist/react/components/checkbox.d.ts +5 -0
  27. package/dist/react/components/checkbox.js +13 -0
  28. package/dist/react/components/chip.d.ts +149 -0
  29. package/dist/react/components/chip.js +29 -0
  30. package/dist/react/components/code-block.d.ts +50 -0
  31. package/dist/react/components/code-block.js +77 -0
  32. package/dist/react/components/code-editor.d.ts +122 -0
  33. package/dist/react/components/code-editor.js +695 -0
  34. package/dist/react/components/column-manager.d.ts +28 -0
  35. package/dist/react/components/column-manager.js +85 -0
  36. package/dist/react/components/combobox.d.ts +32 -0
  37. package/dist/react/components/combobox.js +37 -0
  38. package/dist/react/components/data-grid.d.ts +108 -0
  39. package/dist/react/components/data-grid.js +159 -0
  40. package/dist/react/components/data-table.d.ts +2 -0
  41. package/dist/react/components/data-table.js +6 -0
  42. package/dist/react/components/date.d.ts +23 -0
  43. package/dist/react/components/date.js +11 -0
  44. package/dist/react/components/details.d.ts +13 -0
  45. package/dist/react/components/details.js +20 -0
  46. package/dist/react/components/dialog.d.ts +74 -0
  47. package/dist/react/components/dialog.js +31 -0
  48. package/dist/react/components/diff-view.d.ts +73 -0
  49. package/dist/react/components/diff-view.js +68 -0
  50. package/dist/react/components/documents.d.ts +70 -0
  51. package/dist/react/components/documents.js +10 -0
  52. package/dist/react/components/drawer.d.ts +45 -0
  53. package/dist/react/components/drawer.js +35 -0
  54. package/dist/react/components/editable-grid-cells.d.ts +32 -0
  55. package/dist/react/components/editable-grid-cells.js +116 -0
  56. package/dist/react/components/eyebrow-kicker-lede-pull-quote.d.ts +39 -0
  57. package/dist/react/components/eyebrow-kicker-lede-pull-quote.js +12 -0
  58. package/dist/react/components/field.d.ts +32 -0
  59. package/dist/react/components/field.js +27 -0
  60. package/dist/react/components/file-tree.d.ts +75 -0
  61. package/dist/react/components/file-tree.js +247 -0
  62. package/dist/react/components/file.d.ts +43 -0
  63. package/dist/react/components/file.js +73 -0
  64. package/dist/react/components/files-table.d.ts +174 -0
  65. package/dist/react/components/files-table.js +237 -0
  66. package/dist/react/components/filter-builder.d.ts +37 -0
  67. package/dist/react/components/filter-builder.js +109 -0
  68. package/dist/react/components/form-error-summary.d.ts +16 -0
  69. package/dist/react/components/form-error-summary.js +49 -0
  70. package/dist/react/components/icon-button.d.ts +8 -0
  71. package/dist/react/components/icon-button.js +14 -0
  72. package/dist/react/components/icon.d.ts +7 -0
  73. package/dist/react/components/icon.js +15 -0
  74. package/dist/react/components/image.d.ts +72 -0
  75. package/dist/react/components/image.js +24 -0
  76. package/dist/react/components/inline-edit.d.ts +70 -0
  77. package/dist/react/components/inline-edit.js +149 -0
  78. package/dist/react/components/input.d.ts +29 -0
  79. package/dist/react/components/input.js +20 -0
  80. package/dist/react/components/kanban-board.d.ts +140 -0
  81. package/dist/react/components/kanban-board.js +374 -0
  82. package/dist/react/components/layout.d.ts +51 -0
  83. package/dist/react/components/layout.js +28 -0
  84. package/dist/react/components/list.d.ts +28 -0
  85. package/dist/react/components/list.js +6 -0
  86. package/dist/react/components/masthead-and-verdict.d.ts +60 -0
  87. package/dist/react/components/masthead-and-verdict.js +8 -0
  88. package/dist/react/components/menu.d.ts +26 -0
  89. package/dist/react/components/menu.js +31 -0
  90. package/dist/react/components/multi-select-picker.d.ts +49 -0
  91. package/dist/react/components/multi-select-picker.js +20 -0
  92. package/dist/react/components/note.d.ts +33 -0
  93. package/dist/react/components/note.js +8 -0
  94. package/dist/react/components/number-field.d.ts +16 -0
  95. package/dist/react/components/number-field.js +17 -0
  96. package/dist/react/components/pagination.d.ts +45 -0
  97. package/dist/react/components/pagination.js +87 -0
  98. package/dist/react/components/persistent-action-banner.d.ts +71 -0
  99. package/dist/react/components/persistent-action-banner.js +39 -0
  100. package/dist/react/components/popconfirm.d.ts +42 -0
  101. package/dist/react/components/popconfirm.js +59 -0
  102. package/dist/react/components/popover.d.ts +16 -0
  103. package/dist/react/components/popover.js +21 -0
  104. package/dist/react/components/progress-and-gauge.d.ts +44 -0
  105. package/dist/react/components/progress-and-gauge.js +19 -0
  106. package/dist/react/components/radio.d.ts +7 -0
  107. package/dist/react/components/radio.js +13 -0
  108. package/dist/react/components/rich-text-toolbar.d.ts +26 -0
  109. package/dist/react/components/rich-text-toolbar.js +64 -0
  110. package/dist/react/components/rows-and-card.d.ts +88 -0
  111. package/dist/react/components/rows-and-card.js +10 -0
  112. package/dist/react/components/save-status-indicator.d.ts +11 -0
  113. package/dist/react/components/save-status-indicator.js +33 -0
  114. package/dist/react/components/segmented-control.d.ts +21 -0
  115. package/dist/react/components/segmented-control.js +15 -0
  116. package/dist/react/components/select.d.ts +29 -0
  117. package/dist/react/components/select.js +29 -0
  118. package/dist/react/components/selectable-cards.d.ts +30 -0
  119. package/dist/react/components/selectable-cards.js +29 -0
  120. package/dist/react/components/settings-rows.d.ts +51 -0
  121. package/dist/react/components/settings-rows.js +8 -0
  122. package/dist/react/components/sidebar-nav.d.ts +77 -0
  123. package/dist/react/components/sidebar-nav.js +41 -0
  124. package/dist/react/components/skeleton-and-empty.d.ts +51 -0
  125. package/dist/react/components/skeleton-and-empty.js +14 -0
  126. package/dist/react/components/slider.d.ts +19 -0
  127. package/dist/react/components/slider.js +17 -0
  128. package/dist/react/components/small-charts.d.ts +60 -0
  129. package/dist/react/components/small-charts.js +76 -0
  130. package/dist/react/components/sortable.d.ts +30 -0
  131. package/dist/react/components/sortable.js +85 -0
  132. package/dist/react/components/split-button.d.ts +38 -0
  133. package/dist/react/components/split-button.js +43 -0
  134. package/dist/react/components/split-panes.d.ts +35 -0
  135. package/dist/react/components/split-panes.js +102 -0
  136. package/dist/react/components/stat-tiles.d.ts +54 -0
  137. package/dist/react/components/stat-tiles.js +8 -0
  138. package/dist/react/components/steps.d.ts +11 -0
  139. package/dist/react/components/steps.js +13 -0
  140. package/dist/react/components/switch.d.ts +6 -0
  141. package/dist/react/components/switch.js +13 -0
  142. package/dist/react/components/table.d.ts +61 -0
  143. package/dist/react/components/table.js +6 -0
  144. package/dist/react/components/tabs.d.ts +7 -0
  145. package/dist/react/components/tabs.js +13 -0
  146. package/dist/react/components/tags.d.ts +33 -0
  147. package/dist/react/components/tags.js +83 -0
  148. package/dist/react/components/textarea.d.ts +10 -0
  149. package/dist/react/components/textarea.js +6 -0
  150. package/dist/react/components/toast.d.ts +35 -0
  151. package/dist/react/components/toast.js +90 -0
  152. package/dist/react/components/tooltip-and-key.d.ts +20 -0
  153. package/dist/react/components/tooltip-and-key.js +20 -0
  154. package/dist/react/components/tree-view.d.ts +34 -0
  155. package/dist/react/components/tree-view.js +118 -0
  156. package/dist/react/components/upload-dropzone.d.ts +92 -0
  157. package/dist/react/components/upload-dropzone.js +116 -0
  158. package/dist/react/components/wizard-shell.d.ts +78 -0
  159. package/dist/react/components/wizard-shell.js +176 -0
  160. package/dist/react/fonts.css.d.ts +1 -0
  161. package/dist/react/index.d.ts +76 -0
  162. package/dist/react/index.js +77 -0
  163. package/dist/react/integrations/quickcrop.d.ts +58 -0
  164. package/dist/react/integrations/quickcrop.js +137 -0
  165. package/dist/react/integrations/richclay.d.ts +43 -0
  166. package/dist/react/integrations/richclay.js +129 -0
  167. package/dist/react/internal/classes.d.ts +3 -0
  168. package/dist/react/internal/classes.js +10 -0
  169. package/dist/react/internal/close-mark.d.ts +1 -0
  170. package/dist/react/internal/close-mark.js +10 -0
  171. package/dist/react/internal/controllable-state.d.ts +9 -0
  172. package/dist/react/internal/controllable-state.js +22 -0
  173. package/dist/react/internal/drop-effect.d.ts +28 -0
  174. package/dist/react/internal/drop-effect.js +8 -0
  175. package/dist/react/internal/form-reset.d.ts +2 -0
  176. package/dist/react/internal/form-reset.js +22 -0
  177. package/dist/react/internal/format.d.ts +1 -0
  178. package/dist/react/internal/format.js +14 -0
  179. package/dist/react/internal/link.d.ts +2 -0
  180. package/dist/react/internal/link.js +8 -0
  181. package/dist/react/internal/part.d.ts +20 -0
  182. package/dist/react/internal/part.js +47 -0
  183. package/dist/react/internal/pointer-session.d.ts +8 -0
  184. package/dist/react/internal/pointer-session.js +60 -0
  185. package/dist/react/internal/refs.d.ts +2 -0
  186. package/dist/react/internal/refs.js +22 -0
  187. package/dist/react/internal/rich-text.d.ts +28 -0
  188. package/dist/react/internal/rich-text.js +4 -0
  189. package/dist/react/internal/table-context.d.ts +39 -0
  190. package/dist/react/internal/table-context.js +12 -0
  191. package/dist/react/internal/use-object-url.d.ts +1 -0
  192. package/dist/react/internal/use-object-url.js +15 -0
  193. package/dist/react/layout.css +274 -0
  194. package/dist/react/layout.css.d.ts +1 -0
  195. package/dist/react/models/filter.d.ts +31 -0
  196. package/dist/react/models/filter.js +79 -0
  197. package/dist/react/models/kanban.d.ts +51 -0
  198. package/dist/react/models/kanban.js +35 -0
  199. package/dist/react/models/reorder.d.ts +9 -0
  200. package/dist/react/models/reorder.js +23 -0
  201. package/dist/react/models/table.d.ts +85 -0
  202. package/dist/react/models/table.js +144 -0
  203. package/dist/react/models/tree.d.ts +16 -0
  204. package/dist/react/models/tree.js +26 -0
  205. package/dist/react/provider.d.ts +18 -0
  206. package/dist/react/provider.js +31 -0
  207. package/dist/react/recipes/context-hypercms.d.ts +17 -0
  208. package/dist/react/recipes/context-hypercms.js +71 -0
  209. package/dist/react/recipes/context-quickcrop.d.ts +8 -0
  210. package/dist/react/recipes/context-quickcrop.js +49 -0
  211. package/dist/react/theme.css +8004 -0
  212. package/dist/react/theme.css.d.ts +1 -0
  213. package/examples/README.md +34 -0
  214. package/examples/components/activity-feed.html +28 -0
  215. package/examples/components/app-shell.html +12 -0
  216. package/examples/components/app-toolbar.html +38 -0
  217. package/examples/components/avatar-group.html +27 -0
  218. package/examples/components/breadcrumbs.html +26 -0
  219. package/examples/components/bulk-bar.html +22 -0
  220. package/examples/components/code-block.html +12 -0
  221. package/examples/components/code-editor.html +25 -0
  222. package/examples/components/column-manager.html +9 -0
  223. package/examples/components/combobox.html +13 -0
  224. package/examples/components/context-hypercms.css +41 -0
  225. package/examples/components/context-hypercms.html +40 -0
  226. package/examples/components/context-hypercms.js +62 -0
  227. package/examples/components/context-quickcrop.html +137 -0
  228. package/examples/components/diff-view.html +9 -0
  229. package/examples/components/drawer.html +11 -0
  230. package/examples/components/editable-grid-cells.html +9 -0
  231. package/examples/components/file-browser.html +53 -0
  232. package/examples/components/file-tree.html +34 -0
  233. package/examples/components/files-table.html +80 -0
  234. package/examples/components/filter-builder.html +11 -0
  235. package/examples/components/form-error-summary.html +11 -0
  236. package/examples/components/index.html +51 -0
  237. package/examples/components/inline-edit.html +42 -0
  238. package/examples/components/kanban-board.html +42 -0
  239. package/examples/components/manifest.json +3279 -0
  240. package/examples/components/modal.html +11 -0
  241. package/examples/components/multi-select-picker.html +13 -0
  242. package/examples/components/pagination.html +32 -0
  243. package/examples/components/persistent-action-banner.html +23 -0
  244. package/examples/components/popconfirm.html +13 -0
  245. package/examples/components/popover-menu.html +13 -0
  246. package/examples/components/reference/baseline/DECISIONS.md +85 -0
  247. package/examples/components/reference/baseline/README.md +58 -0
  248. package/examples/components/reference/baseline/bevel-fonts.css +16 -0
  249. package/examples/components/reference/baseline/bevel.css +1339 -0
  250. package/examples/components/reference/baseline/bevel.js +496 -0
  251. package/examples/components/reference/baseline/fonts/LICENSE-ibm-plex-mono.txt +93 -0
  252. package/examples/components/reference/baseline/fonts/LICENSE-ibm-plex-sans.txt +93 -0
  253. package/examples/components/reference/baseline/fonts/LICENSE-newsreader.txt +93 -0
  254. package/examples/components/reference/baseline/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  255. package/examples/components/reference/baseline/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  256. package/examples/components/reference/baseline/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  257. package/examples/components/reference/baseline/fonts/ibm-plex-sans-latin-400-italic.woff2 +0 -0
  258. package/examples/components/reference/baseline/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  259. package/examples/components/reference/baseline/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  260. package/examples/components/reference/baseline/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  261. package/examples/components/reference/baseline/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
  262. package/examples/components/reference/baseline/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  263. package/examples/components/reference/fable/IMPROVEMENTS.md +106 -0
  264. package/examples/components/reference/fable/PARENT-REPAIRS.md +17 -0
  265. package/examples/components/reference/fable/__pycache__/build_repairs.cpython-311.pyc +0 -0
  266. package/examples/components/reference/fable/__pycache__/kanban.cpython-311.pyc +0 -0
  267. package/examples/components/reference/fable/as-authored/IMPROVEMENTS-completed.md +106 -0
  268. package/examples/components/reference/fable/as-authored/base-astra.css +17 -0
  269. package/examples/components/reference/fable/as-authored/base-parent.css +416 -0
  270. package/examples/components/reference/fable/as-authored/build.py +858 -0
  271. package/examples/components/reference/fable/as-authored/fable.css +823 -0
  272. package/examples/components/reference/fable/as-authored/kanban-completed.css +235 -0
  273. package/examples/components/reference/fable/as-authored/kanban-completed.html +53 -0
  274. package/examples/components/reference/fable/as-authored/kanban-completed.py +212 -0
  275. package/examples/components/reference/fable/as-authored/kanban.py +207 -0
  276. package/examples/components/reference/fable/base-astra.css +17 -0
  277. package/examples/components/reference/fable/base-parent.css +416 -0
  278. package/examples/components/reference/fable/build.py +858 -0
  279. package/examples/components/reference/fable/build_repairs.py +25 -0
  280. package/examples/components/reference/fable/components/activity-feed.html +3 -0
  281. package/examples/components/reference/fable/components/app-toolbar.html +3 -0
  282. package/examples/components/reference/fable/components/avatar-group.html +4 -0
  283. package/examples/components/reference/fable/components/breadcrumbs.html +3 -0
  284. package/examples/components/reference/fable/components/code-block.html +9 -0
  285. package/examples/components/reference/fable/components/column-manager.html +3 -0
  286. package/examples/components/reference/fable/components/combobox.html +6 -0
  287. package/examples/components/reference/fable/components/context-hypercms.html +3 -0
  288. package/examples/components/reference/fable/components/context-quickcrop.html +4 -0
  289. package/examples/components/reference/fable/components/diff-view.html +13 -0
  290. package/examples/components/reference/fable/components/drawer.html +3 -0
  291. package/examples/components/reference/fable/components/editable-grid-cells.html +3 -0
  292. package/examples/components/reference/fable/components/filter-builder.html +3 -0
  293. package/examples/components/reference/fable/components/form-error-summary.html +4 -0
  294. package/examples/components/reference/fable/components/inline-edit.html +3 -0
  295. package/examples/components/reference/fable/components/kanban-board.html +53 -0
  296. package/examples/components/reference/fable/components/modal.html +3 -0
  297. package/examples/components/reference/fable/components/multi-select-picker.html +5 -0
  298. package/examples/components/reference/fable/components/pagination.html +3 -0
  299. package/examples/components/reference/fable/components/persistent-action-banner.html +4 -0
  300. package/examples/components/reference/fable/components/popconfirm.html +5 -0
  301. package/examples/components/reference/fable/components/popover-menu.html +3 -0
  302. package/examples/components/reference/fable/components/rich-text-toolbar.html +3 -0
  303. package/examples/components/reference/fable/components/save-status-indicator.html +2 -0
  304. package/examples/components/reference/fable/components/selectable-cards.html +3 -0
  305. package/examples/components/reference/fable/components/settings-rows.html +3 -0
  306. package/examples/components/reference/fable/components/sidebar-nav.html +4 -0
  307. package/examples/components/reference/fable/components/small-charts.html +5 -0
  308. package/examples/components/reference/fable/components/split-button.html +4 -0
  309. package/examples/components/reference/fable/components/split-panes.html +8 -0
  310. package/examples/components/reference/fable/components/stat-tiles.html +3 -0
  311. package/examples/components/reference/fable/components/toast-stack.html +5 -0
  312. package/examples/components/reference/fable/components/tree-view.html +4 -0
  313. package/examples/components/reference/fable/components/upload-dropzone.html +6 -0
  314. package/examples/components/reference/fable/components/wizard-shell.html +3 -0
  315. package/examples/components/reference/fable/coverage.json +414 -0
  316. package/examples/components/reference/fable/fable.css +823 -0
  317. package/examples/components/reference/fable/index.html +5 -0
  318. package/examples/components/reference/fable/kanban.css +237 -0
  319. package/examples/components/reference/fable/kanban.py +212 -0
  320. package/examples/components/reference/fable/parent-repairs.css +9 -0
  321. package/examples/components/reference/fixtures/data.json +102 -0
  322. package/examples/components/reference/fixtures/host.js +47 -0
  323. package/examples/components/reference/fixtures/static-host.js +39 -0
  324. package/examples/components/reference/fixtures/workspace.jpg +0 -0
  325. package/examples/components/reference/parent/CHANGES.md +179 -0
  326. package/examples/components/reference/parent/__pycache__/kanban.cpython-311.pyc +0 -0
  327. package/examples/components/reference/parent/base-astra.css +17 -0
  328. package/examples/components/reference/parent/base-parent.css +416 -0
  329. package/examples/components/reference/parent/build.py +148 -0
  330. package/examples/components/reference/parent/components/activity-feed.html +2 -0
  331. package/examples/components/reference/parent/components/app-toolbar.html +2 -0
  332. package/examples/components/reference/parent/components/avatar-group.html +4 -0
  333. package/examples/components/reference/parent/components/breadcrumbs.html +2 -0
  334. package/examples/components/reference/parent/components/code-block.html +8 -0
  335. package/examples/components/reference/parent/components/column-manager.html +2 -0
  336. package/examples/components/reference/parent/components/combobox.html +6 -0
  337. package/examples/components/reference/parent/components/context-hypercms.html +2 -0
  338. package/examples/components/reference/parent/components/context-quickcrop.html +4 -0
  339. package/examples/components/reference/parent/components/diff-view.html +4 -0
  340. package/examples/components/reference/parent/components/drawer.html +2 -0
  341. package/examples/components/reference/parent/components/editable-grid-cells.html +2 -0
  342. package/examples/components/reference/parent/components/filter-builder.html +2 -0
  343. package/examples/components/reference/parent/components/form-error-summary.html +2 -0
  344. package/examples/components/reference/parent/components/inline-edit.html +2 -0
  345. package/examples/components/reference/parent/components/kanban-board.html +84 -0
  346. package/examples/components/reference/parent/components/modal.html +2 -0
  347. package/examples/components/reference/parent/components/multi-select-picker.html +5 -0
  348. package/examples/components/reference/parent/components/pagination.html +2 -0
  349. package/examples/components/reference/parent/components/persistent-action-banner.html +4 -0
  350. package/examples/components/reference/parent/components/popconfirm.html +5 -0
  351. package/examples/components/reference/parent/components/popover-menu.html +2 -0
  352. package/examples/components/reference/parent/components/rich-text-toolbar.html +2 -0
  353. package/examples/components/reference/parent/components/save-status-indicator.html +2 -0
  354. package/examples/components/reference/parent/components/selectable-cards.html +2 -0
  355. package/examples/components/reference/parent/components/settings-rows.html +2 -0
  356. package/examples/components/reference/parent/components/sidebar-nav.html +4 -0
  357. package/examples/components/reference/parent/components/small-charts.html +2 -0
  358. package/examples/components/reference/parent/components/split-button.html +5 -0
  359. package/examples/components/reference/parent/components/split-panes.html +8 -0
  360. package/examples/components/reference/parent/components/stat-tiles.html +2 -0
  361. package/examples/components/reference/parent/components/toast-stack.html +5 -0
  362. package/examples/components/reference/parent/components/tree-view.html +4 -0
  363. package/examples/components/reference/parent/components/upload-dropzone.html +6 -0
  364. package/examples/components/reference/parent/components/wizard-shell.html +2 -0
  365. package/examples/components/reference/parent/coverage.json +414 -0
  366. package/examples/components/reference/parent/index.html +1 -0
  367. package/examples/components/reference/parent/kanban.css +114 -0
  368. package/examples/components/reference/parent/kanban.py +55 -0
  369. package/examples/components/reference/parent/refinement.css +53 -0
  370. package/examples/components/reference/selected/build.py +70 -0
  371. package/examples/components/reference/selected/components/activity-feed.html +3 -0
  372. package/examples/components/reference/selected/components/app-toolbar.html +3 -0
  373. package/examples/components/reference/selected/components/avatar-group.html +4 -0
  374. package/examples/components/reference/selected/components/breadcrumbs.html +3 -0
  375. package/examples/components/reference/selected/components/code-block.html +9 -0
  376. package/examples/components/reference/selected/components/column-manager.html +3 -0
  377. package/examples/components/reference/selected/components/combobox.html +6 -0
  378. package/examples/components/reference/selected/components/context-hypercms.html +3 -0
  379. package/examples/components/reference/selected/components/context-quickcrop.html +4 -0
  380. package/examples/components/reference/selected/components/diff-view.html +13 -0
  381. package/examples/components/reference/selected/components/drawer.html +3 -0
  382. package/examples/components/reference/selected/components/editable-grid-cells.html +3 -0
  383. package/examples/components/reference/selected/components/filter-builder.html +3 -0
  384. package/examples/components/reference/selected/components/form-error-summary.html +4 -0
  385. package/examples/components/reference/selected/components/inline-edit.html +3 -0
  386. package/examples/components/reference/selected/components/kanban-board.html +53 -0
  387. package/examples/components/reference/selected/components/modal.html +2 -0
  388. package/examples/components/reference/selected/components/multi-select-picker.html +5 -0
  389. package/examples/components/reference/selected/components/pagination.html +3 -0
  390. package/examples/components/reference/selected/components/persistent-action-banner.html +4 -0
  391. package/examples/components/reference/selected/components/popconfirm.html +5 -0
  392. package/examples/components/reference/selected/components/popover-menu.html +3 -0
  393. package/examples/components/reference/selected/components/rich-text-toolbar.html +3 -0
  394. package/examples/components/reference/selected/components/save-status-indicator.html +2 -0
  395. package/examples/components/reference/selected/components/selectable-cards.html +3 -0
  396. package/examples/components/reference/selected/components/settings-rows.html +3 -0
  397. package/examples/components/reference/selected/components/sidebar-nav.html +4 -0
  398. package/examples/components/reference/selected/components/small-charts.html +5 -0
  399. package/examples/components/reference/selected/components/split-button.html +4 -0
  400. package/examples/components/reference/selected/components/split-panes.html +8 -0
  401. package/examples/components/reference/selected/components/stat-tiles.html +3 -0
  402. package/examples/components/reference/selected/components/toast-stack.html +5 -0
  403. package/examples/components/reference/selected/components/tree-view.html +4 -0
  404. package/examples/components/reference/selected/components/upload-dropzone.html +6 -0
  405. package/examples/components/reference/selected/components/wizard-shell.html +3 -0
  406. package/examples/components/reference/selected/corrections.css +11 -0
  407. package/examples/components/reference/selected/coverage.json +413 -0
  408. package/examples/components/reference/selected/gallery-build.mjs +49 -0
  409. package/examples/components/reference/selected/gallery-spec.json +12 -0
  410. package/examples/components/reference/selected/index.html +1 -0
  411. package/examples/components/reference/selected/kanban-comparison.json +6 -0
  412. package/examples/components/reference/selected/manifest.json +476 -0
  413. package/examples/components/reference/sha256.json +142 -0
  414. package/examples/components/reference/sources/diagonal-close.svg +1 -0
  415. package/examples/components/reference/sources/hyperclay/public-assets/css/hyperclay-app-source.css +614 -0
  416. package/examples/components/reference/sources/hyperclay/public-assets/js/app.js +94 -0
  417. package/examples/components/reference/sources/hyperclayjs/src/ui/theModal.js +897 -0
  418. package/examples/components/reference/sources/hypercms/scripts/build-theme.js +203 -0
  419. package/examples/components/reference/sources/hypercms/src/anchor.js +24 -0
  420. package/examples/components/reference/sources/hypercms/src/apply-loop.js +167 -0
  421. package/examples/components/reference/sources/hypercms/src/diagnostics.js +44 -0
  422. package/examples/components/reference/sources/hypercms/src/enhance.js +173 -0
  423. package/examples/components/reference/sources/hypercms/src/events.js +955 -0
  424. package/examples/components/reference/sources/hypercms/src/form-builder.js +518 -0
  425. package/examples/components/reference/sources/hypercms/src/form-rules.js +208 -0
  426. package/examples/components/reference/sources/hypercms/src/hooks.js +100 -0
  427. package/examples/components/reference/sources/hypercms/src/hypercms-bundle.js +10 -0
  428. package/examples/components/reference/sources/hypercms/src/hypercms.js +347 -0
  429. package/examples/components/reference/sources/hypercms/src/morph.js +10 -0
  430. package/examples/components/reference/sources/hypercms/src/path.js +61 -0
  431. package/examples/components/reference/sources/hypercms/src/place.js +131 -0
  432. package/examples/components/reference/sources/hypercms/src/platform.js +131 -0
  433. package/examples/components/reference/sources/hypercms/src/refresh.js +87 -0
  434. package/examples/components/reference/sources/hypercms/src/richclay-bridge.js +140 -0
  435. package/examples/components/reference/sources/hypercms/src/row-identity.js +111 -0
  436. package/examples/components/reference/sources/hypercms/src/scaffold.js +10 -0
  437. package/examples/components/reference/sources/hypercms/src/session.js +303 -0
  438. package/examples/components/reference/sources/hypercms/src/shell.js +248 -0
  439. package/examples/components/reference/sources/hypercms/src/styles.css +298 -0
  440. package/examples/components/reference/sources/hypercms/src/targets.js +144 -0
  441. package/examples/components/reference/sources/hypercms/src/templates.js +656 -0
  442. package/examples/components/reference/sources/hypercms/src/theme/pixel-quiet.overrides.css +761 -0
  443. package/examples/components/reference/sources/hypercms/src/theme.generated.css +1772 -0
  444. package/examples/components/reference/sources/hypercms/src/toggle.js +666 -0
  445. package/examples/components/reference/sources/hypercms/src/unresolved.js +211 -0
  446. package/examples/components/reference/sources/hypercms/src/vendor/mirk.vendor.js +212 -0
  447. package/examples/components/reference/sources/hypercms/src/views/inline-layer.js +421 -0
  448. package/examples/components/reference/sources/hypercms/src/views/inline.js +1072 -0
  449. package/examples/components/reference/sources/hypercms/src/views/sidebar.js +97 -0
  450. package/examples/components/reference/sources/modal-reference.html +301 -0
  451. package/examples/components/reference/sources/quickcrop/quickcrop.css +124 -0
  452. package/examples/components/reference/sources/quickcrop/quickcrop.js +400 -0
  453. package/examples/components/reference/sources/richclay/richclay.css +416 -0
  454. package/examples/components/reference/sources/richclay/richclay.js +2962 -0
  455. package/examples/components/reference/sources/richclay/src/a11y.js +67 -0
  456. package/examples/components/reference/sources/richclay/src/browser-global.js +7 -0
  457. package/examples/components/reference/sources/richclay/src/buttons.js +254 -0
  458. package/examples/components/reference/sources/richclay/src/hyperclay.js +470 -0
  459. package/examples/components/reference/sources/richclay/src/normalize.js +380 -0
  460. package/examples/components/reference/sources/richclay/src/richclay.js +1356 -0
  461. package/examples/components/reference/sources/richclay/src/sanitize.js +189 -0
  462. package/examples/components/reference/sources/richclay/src/styles.js +23 -0
  463. package/examples/components/reference/sources/richclay/src/toolbar-float.js +175 -0
  464. package/examples/components/reference/sources/richclay/src/toolbar.js +394 -0
  465. package/examples/components/reference/sources/richclay/src/vendor-bundle.js +15 -0
  466. package/examples/components/reference/sources/richclay/vendor/DOMPURIFY-LICENSE +202 -0
  467. package/examples/components/reference/sources/richclay/vendor/DOMPURIFY-LICENSE-MPL +373 -0
  468. package/examples/components/reference/sources/richclay/vendor/SQUIRE-LICENSE +21 -0
  469. package/examples/components/reference/sources/richclay/vendor/purify.es.mjs +2249 -0
  470. package/examples/components/reference/sources/richclay/vendor/purify.min.js +3 -0
  471. package/examples/components/reference/sources/richclay/vendor/squire.js +22 -0
  472. package/examples/components/reference/sources/richclay/vendor/squire.mjs +22 -0
  473. package/examples/components/reference/sources/richclay/vendor/squire.provenance.json +19 -0
  474. package/examples/components/rich-text-toolbar.html +43 -0
  475. package/examples/components/save-status-indicator.html +11 -0
  476. package/examples/components/selectable-cards.html +9 -0
  477. package/examples/components/settings-rows.html +10 -0
  478. package/examples/components/sidebar-nav.html +9 -0
  479. package/examples/components/small-charts.html +21 -0
  480. package/examples/components/split-button.html +13 -0
  481. package/examples/components/split-panes-editor.html +6 -0
  482. package/examples/components/split-panes.html +9 -0
  483. package/examples/components/stat-tiles.html +23 -0
  484. package/examples/components/toast-stack.html +21 -0
  485. package/examples/components/tree-view.html +9 -0
  486. package/examples/components/upload-dropzone.html +88 -0
  487. package/examples/components/wizard-shell.html +50 -0
  488. package/examples/datastar/index.html +38 -0
  489. package/examples/datastar/server.mjs +31 -0
  490. package/examples/fixtures/field-guide.pdf +14 -0
  491. package/examples/fixtures/workspace.jpg +0 -0
  492. package/examples/setup-peers.mjs +35 -0
  493. package/fonts/LICENSE-ibm-plex-mono.txt +93 -0
  494. package/fonts/LICENSE-ibm-plex-sans.txt +93 -0
  495. package/fonts/LICENSE-newsreader.txt +93 -0
  496. package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  497. package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  498. package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  499. package/fonts/ibm-plex-sans-latin-400-italic.woff2 +0 -0
  500. package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
  501. package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
  502. package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
  503. package/fonts/newsreader-latin-opsz-italic.woff2 +0 -0
  504. package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
  505. package/icons/LICENSE +21 -0
  506. package/icons/house.json +71 -0
  507. package/icons/icons.d.ts +17 -0
  508. package/icons/icons.js +648 -0
  509. package/icons/icons.svg +126 -0
  510. package/icons/marks/build-tray.source.mjs +45 -0
  511. package/icons/marks/hyperclay-local.svg +11 -0
  512. package/icons/phosphor.json +53 -0
  513. package/icons/round.json +8 -0
  514. package/integrations/datastar.js +202 -0
  515. package/integrations/quickcrop.css +26 -0
  516. package/integrations/quickcrop.js +56 -0
  517. package/integrations/richclay.css +100 -0
  518. package/integrations/richclay.js +131 -0
  519. package/package.json +630 -0
  520. package/react/README.md +244 -0
  521. package/src/README.md +83 -0
  522. package/vendor/datastar/datastar-1.0.3.js +10 -0
  523. package/vendor/datastar/provenance.json +6 -0
  524. package/vendor/floating-ui/LICENSE-core +20 -0
  525. package/vendor/floating-ui/LICENSE-dom +20 -0
  526. package/vendor/floating-ui/floating-ui.core.umd.min.js +1 -0
  527. package/vendor/floating-ui/floating-ui.dom.umd.min.js +1 -0
  528. package/vendor/floating-ui/provenance.json +17 -0
  529. package/vendor/richclay/LICENSE +16 -0
  530. package/vendor/richclay/THIRD-PARTY-NOTICES.md +45 -0
  531. package/vendor/richclay/provenance.json +17 -0
  532. package/vendor/richclay/richclay.css +404 -0
  533. package/vendor/richclay/richclay.js +2940 -0
  534. package/vendor/richclay/vendor/DOMPURIFY-LICENSE +202 -0
  535. package/vendor/richclay/vendor/DOMPURIFY-LICENSE-MPL +373 -0
  536. package/vendor/richclay/vendor/SQUIRE-LICENSE +21 -0
  537. package/vendor/richclay/vendor/purify.min.js +3 -0
  538. package/vendor/richclay/vendor/squire.js +22 -0
  539. package/vendor/richclay/vendor/squire.provenance.json +30 -0
@@ -0,0 +1,858 @@
1
+ """Fable seat build. Reads the parent's corrected static components (read-only
2
+ input), applies Fable's structural refinements with BeautifulSoup, links
3
+ fable.css, and writes components/, index.html and coverage.json.
4
+
5
+ python3 build.py
6
+ """
7
+ from pathlib import Path
8
+ from bs4 import BeautifulSoup
9
+ import json
10
+ from kanban import kanban, icon, avatar, button
11
+
12
+ ROOT = Path(__file__).resolve().parent.parent
13
+ HERE = ROOT / 'fable'
14
+ PARENT = ROOT / 'parent' / 'components'
15
+ MANIFEST = json.loads((ROOT / 'manifest.json').read_text())
16
+ FEEDBACK = {a['key']: a for a in json.loads((ROOT / 'feedback.json').read_text())['answers']}
17
+ CLOSE_SVG = (ROOT / 'sources/diagonal-close.svg').read_text().strip()
18
+ CLOSE_TOKEN = 'EXACT_DIAGONAL_CLOSE_SVG'
19
+
20
+
21
+ def frag(html):
22
+ return BeautifulSoup(html, 'html.parser')
23
+
24
+
25
+ def replace_contents(node, html):
26
+ node.clear()
27
+ for child in list(frag(html).contents):
28
+ node.append(child)
29
+
30
+
31
+ def replace_with(node, html):
32
+ new = list(frag(html).contents)
33
+ for child in new:
34
+ node.insert_before(child)
35
+ node.decompose()
36
+
37
+
38
+ def append_html(node, html):
39
+ for child in list(frag(html).contents):
40
+ node.append(child)
41
+
42
+
43
+ def prepend_html(node, html):
44
+ for child in reversed(list(frag(html).contents)):
45
+ node.insert(0, child)
46
+
47
+
48
+ def set_label(btn, text):
49
+ span = btn.select_one('.bevel-button__label')
50
+ if span: span.string = text
51
+
52
+
53
+ def icon_button(name, label, extra='', size=16, pressed=None):
54
+ p = f' aria-pressed="{pressed}"' if pressed is not None else ''
55
+ return f'<button type="button" class="f-icon-button {extra}" aria-label="{label}" title="{label}"{p}>{icon(name, size)}</button>'
56
+
57
+
58
+ INVOICE_ROWS = [('INV-0431', 'Juniper Health', '$1,240.00', 'Paid', 'good'), ('INV-0432', 'Northstar Studio', '$3,200.00', 'Overdue', 'bad'),
59
+ ('INV-0433', 'Maple House', '$860.00', 'Sent', ''), ('INV-0434', 'Juniper Health', '$410.00', 'Due soon', '')]
60
+
61
+
62
+ def invoice_table(amount_cell=None):
63
+ rows = ''
64
+ for inv, client, amount, status, tone in INVOICE_ROWS:
65
+ cell = amount_cell if (amount_cell and inv == 'INV-0432') else f'<td class="numeric">{amount}</td>'
66
+ rows += f'<tr><td class="mono">{inv}</td><td>{client}</td>{cell}<td><span class="badge {tone}">{status}</span></td></tr>'
67
+ return f'<div class="table-scroll"><table><thead><tr><th>Invoice</th><th>Client</th><th style="text-align:right">Amount</th><th>Status</th></tr></thead><tbody>{rows}</tbody></table></div>'
68
+
69
+
70
+ CODE_LINES = [
71
+ '<span class="tk-tag">&lt;article</span> <span class="tk-attr">class</span>=<span class="tk-str">"field-note"</span><span class="tk-tag">&gt;</span>',
72
+ ' <span class="tk-tag">&lt;h1&gt;</span>A place for good work<span class="tk-tag">&lt;/h1&gt;</span>',
73
+ ' <span class="tk-tag">&lt;p</span> <span class="tk-attr">data-description</span>=<span class="tk-str">"Documents, decisions and the small details that make a project feel like home"</span><span class="tk-tag">&gt;</span>Keep the useful things close.<span class="tk-tag">&lt;/p&gt;</span>',
74
+ '<span class="tk-tag">&lt;/article&gt;</span>',
75
+ ]
76
+
77
+
78
+ def refine(soup, key):
79
+ states = {s['data-ref-state']: s for s in soup.select('[data-ref-state]')}
80
+
81
+ if key == 'modal':
82
+ for node in soup.select('.micromodal__close'):
83
+ replace_contents(node, CLOSE_TOKEN)
84
+ for h in soup.select('.micromodal__container .well'):
85
+ replace_with(h, f'<div class="f-archive-card">{icon("doc", 22)}<div><strong>Field notes</strong><small>12 documents · 4 collaborators · last edited today</small></div><span class="badge">Active</span></div>')
86
+ for sc in soup.select('.scroll-copy'):
87
+ sc['class'] = ['scroll-copy', 'f-scroll-copy']
88
+ sc.insert_after(frag('<div class="f-scroll-hint">Scroll to read the rest</div>'))
89
+
90
+ elif key == 'popover-menu':
91
+ for btn in soup.select('.anchor-stage .row > button'):
92
+ replace_with(btn, icon_button('more', 'Project actions', 'f-menu-trigger', 18) .replace('<button ', f'<button aria-expanded="{btn.get("aria-expanded","false")}" '))
93
+ names = {'Open project': 'external', 'Rename': 'pencil', 'Duplicate': 'copy', 'Move to workspace': 'arrow', 'Archive project': 'archive'}
94
+ for item in soup.select('.menu button'):
95
+ glyph = item.select_one('span')
96
+ text = ''.join(t for t in item.find_all(string=True, recursive=False)).strip()
97
+ if glyph: replace_contents(glyph, icon(names.get(text, 'doc'), 15))
98
+ kbd = item.select_one('kbd')
99
+ if kbd and not kbd.get_text().strip(): kbd.decompose()
100
+ for menu in soup.select('.menu'):
101
+ title = menu.select_one('.menu-title')
102
+ if title: title.string = 'Field notes · Project'
103
+ for st in soup.select('[data-ref-state="closed"] .anchor-stage'):
104
+ append_html(st, '<p class="f-anchor-note">Press the actions button to open the project menu.</p>')
105
+
106
+ elif key == 'drawer':
107
+ for panel in soup.select('.drawer-panel'):
108
+ close = panel.select_one('.r-diagonal-close')
109
+ replace_contents(close, CLOSE_TOKEN)
110
+ amount_row = panel.select_one('header .row.between[style]')
111
+ if amount_row:
112
+ del amount_row['style']
113
+ amount_row['class'] = ['f-drawer-amount']
114
+ for dl in soup.select('.detail-grid'):
115
+ dl['class'] = ['detail-grid', 'f-detail-grid']
116
+ for under in soup.select('.drawer-underlay'):
117
+ replace_contents(under, '<div class="eyebrow">Workspace</div><h2>Invoices</h2><p class="note">4 records · Northstar Studio</p>'
118
+ '<div class="f-underlay-row"><span class="mono">INV-0431</span><span>Juniper Health</span></div>'
119
+ '<div class="f-underlay-row is-open"><span class="mono">INV-0432</span><span>Northstar Studio</span></div>'
120
+ '<div class="f-underlay-row"><span class="mono">INV-0433</span><span>Maple House</span></div>'
121
+ '<div class="f-underlay-row"><span class="mono">INV-0434</span><span>Juniper Health</span></div>')
122
+ for h3 in soup.select('.drawer-panel main h3'):
123
+ replace_with(h3, '<div class="eyebrow">Activity</div>')
124
+ closed = states['closed']
125
+ btn = closed.select_one('.specimen > .bevel-button')
126
+ if btn:
127
+ replace_with(btn, f'<div class="rule row between wrap"><small>Choose a record to open its details.</small>{button("View INV-0432", "external")}</div>')
128
+
129
+ elif key == 'popconfirm':
130
+ for st, sec in states.items():
131
+ trigger = sec.select_one('.p-between .p-icon-button')
132
+ if trigger:
133
+ replace_with(trigger, icon_button('trash', 'Remove document', 'f-popconfirm-trigger', 17).replace('<button ', f'<button aria-expanded="{"true" if st == "open" else "false"}" '))
134
+ for pc in soup.select('.p-popconfirm'):
135
+ head = pc.select_one('.p-row')
136
+ head['class'] = ['f-popconfirm-head']
137
+ svg = head.select_one('svg')
138
+ if svg: replace_with(svg, icon('warning', 18))
139
+ set_label(pc.select_one('.bevel-button--danger'), 'Remove')
140
+ for row in soup.select('[data-ref-state="confirmed"] .p-row[style]'):
141
+ replace_with(row, f'<div class="f-status-line good">{icon("check", 16)}<span><strong>Document moved to the archive.</strong> Field notes is no longer in this project.</span>{button("Undo", None, "bevel-button--small")}</div>')
142
+ for p in soup.select('[data-ref-state="closed"] p[style]'):
143
+ replace_with(p, '<div class="f-status-line"><span>Everything is up to date.</span></div>')
144
+
145
+ elif key == 'combobox':
146
+ colors = {'MC': 'MC', 'AR': 'AR', 'SP': 'SP', 'JP': 'JP'}
147
+ for opt in soup.select('.p-option'):
148
+ av = opt.select_one('.p-avatar')
149
+ if av:
150
+ av['class'] = ['f-avatar', 'small']
151
+ av['data-who'] = av.get_text().strip()
152
+ for opt in soup.select('[data-ref-state="filtered"] .p-option:not([hidden]) strong'):
153
+ opt.string = ''
154
+ append_html(opt, '<mark>Mi</mark>ra Chen')
155
+ for search in soup.select('.p-search'):
156
+ btn = search.select_one('.p-icon-button')
157
+ expanded = search.select_one('input').get('aria-expanded', 'false')
158
+ replace_with(btn, icon_button('chevron', 'Open choices', 'f-combo-toggle', 15).replace('<button ', f'<button aria-expanded="{expanded}" '))
159
+ search['class'] = ['p-search', 'f-search']
160
+ if expanded == 'true': search['class'].append('is-open')
161
+ closed = states['closed'].select_one('.f-search')
162
+ closed.select_one('svg').replace_with(frag(avatar('MC', 'small')))
163
+ for e in soup.select('.p-empty'):
164
+ e['class'] = ['p-empty', 'f-empty']
165
+ prepend_html(e, icon('search', 22))
166
+ e.select_one('p').string = 'No member matches “Taylor”. Check the spelling or invite them.'
167
+ for foot in soup.select('.p-picker-foot'):
168
+ foot.string = '4 members in Northstar Studio · ↑↓ to move, ↵ to choose'
169
+
170
+ elif key == 'multi-select-picker':
171
+ tone = {'Design': 'design', 'Editorial': 'editorial', 'Engineering': 'engineering', 'Research': 'research'}
172
+ for tag in soup.select('.p-tag'):
173
+ tag['class'] = ['p-tag', 'f-tag']
174
+ tag['data-label'] = tone[tag['data-tag']]
175
+ btn = tag.select_one('button')
176
+ svg = btn.select_one('svg')
177
+ if svg: replace_with(svg, icon('close', 11))
178
+ for opt in soup.select('.p-option'):
179
+ lbl = opt.select_one('.bevel-checkbox')
180
+ name = lbl.select_one('span:last-child').get_text()
181
+ lbl.select_one('span:last-child').insert_before(frag(f'<i class="f-label-dot" data-label="{tone[name]}"></i>'))
182
+ for act in soup.select('[data-ref-state="selected"] .p-actions'):
183
+ act.decompose()
184
+ for panel in soup.select('.p-panel[style]'):
185
+ panel['class'] = ['f-tag-field']
186
+ del panel['style']
187
+ for sec_key in ['open', 'empty']:
188
+ field = states[sec_key].select_one('.f-tag-field')
189
+ field['class'].append('is-open')
190
+ for wrap in soup.select('.f-tag-field + div:not([hidden])'):
191
+ wrap['class'] = ['f-tag-popover']
192
+ for ph in soup.select('[data-tag-placeholder]'):
193
+ ph.string = 'Type to find a label'
194
+ for e in soup.select('[data-ref-state="empty"] .p-empty'):
195
+ e['class'] = ['p-empty', 'f-empty']
196
+ e.select_one('h3').string = 'No label called “Finance”'
197
+ e.select_one('p').string = 'Try Design, Editorial or Research, or make a new one.'
198
+ append_html(e, button('Create “Finance”', 'plus', 'bevel-button--small'))
199
+ for foot in soup.select('.p-picker-foot'):
200
+ n = foot.select_one('[data-tag-count]').get_text()
201
+ foot.string = ''
202
+ append_html(foot, f'<span data-tag-count="">{n}</span> of 4 labels selected')
203
+
204
+ elif key == 'selectable-cards':
205
+ for ch in soup.select('.choice'):
206
+ badge = ch.select_one('.badge')
207
+ if badge and badge.get_text().strip() == 'Recommended': badge['class'] = ['badge', 'accent']
208
+ if badge and badge.get_text().strip() == 'Unavailable':
209
+ prepend_html(badge, icon('lock', 10))
210
+ for st in soup.select('[data-ref-state="checkbox"] .specimen > header p'):
211
+ st.string = 'Bring the right disciplines into the conversation. Choose as many as you need.'
212
+
213
+ elif key == 'upload-dropzone':
214
+ for dz in soup.select('.p-dropzone'):
215
+ svg = dz.select_one(':scope > svg')
216
+ if svg: replace_with(svg, icon('upload', 26))
217
+ for sec in ['queue', 'error', 'crop']:
218
+ dz = states[sec].select_one('.p-dropzone')
219
+ dz['class'] = ['p-dropzone', 'is-compact']
220
+ for row in soup.select('.p-upload-row'):
221
+ row['class'] = ['p-upload-row', 'f-upload-row']
222
+ svg = row.select_one(':scope > svg')
223
+ if svg and svg.get('height') == '26': replace_with(svg, f'<span class="f-file-icon">{icon("doc", 20)}</span>')
224
+ check = row.select_one(':scope > svg[height="18"]')
225
+ if check: replace_with(check, f'<span class="f-file-state good">{icon("check", 14)}Ready</span>')
226
+ pct = row.select_one('.p-badge')
227
+ if pct: pct['class'] = ['f-file-pct']
228
+ retry = row.select_one('.bevel-button')
229
+ if retry:
230
+ replace_with(retry, f'<span class="f-file-actions"><span class="f-file-state bad">{icon("warning", 14)}Failed</span>{button("Retry", "undo", "bevel-button--small")}</span>')
231
+ row.select_one('p').string = '184 KB · The connection dropped at 68%'
232
+ for st in states['queue'].select('.p-upload-row p'):
233
+ if 'Uploading' in st.get_text(): st.string = '184 KB · Uploading'
234
+ crop_head = states['crop'].select_one('.p-between[style]')
235
+ if crop_head:
236
+ del crop_head['style']
237
+ crop_head['class'] = ['p-between', 'f-crop-head']
238
+ crop_head.select_one('.p-badge').string = '1:1 · 800 × 800'
239
+ for act in states['crop'].select('.p-actions'):
240
+ set_label(act.select_one('.bevel-button--primary'), 'Use this crop')
241
+ set_label(act.select_one('.bevel-button:not(.bevel-button--primary)'), 'Cancel')
242
+
243
+ elif key == 'inline-edit':
244
+ for wrap in soup.select('.specimen > div[style]'):
245
+ del wrap['style']
246
+ wrap['class'] = ['f-inline-wrap']
247
+ for sec in ['editing', 'error']:
248
+ h2 = states[sec].select_one('.r-inline-name')
249
+ h2.insert_before(frag('<div class="f-inline-hint"><span class="eyebrow">Project name</span><span class="f-keys"><kbd>↵</kbd> save <kbd>esc</kbd> cancel</span></div>'))
250
+ states[sec].select_one('.f-inline-wrap > .eyebrow').decompose()
251
+ note = states[sec].select_one('.note')
252
+ if note: note.decompose()
253
+ err = states['error'].select_one('p.bad')
254
+ if err:
255
+ replace_with(err, f'<p class="f-field-error">{icon("warning", 13)}Give this project a name before saving.</p>')
256
+ saved = states['saved'].select_one('.note')
257
+ if saved:
258
+ replace_with(saved, f'<p class="f-status-line good f-inline-saved">{icon("check", 14)}<span>Saved at 10:42</span></p>')
259
+ for st in soup.select('[data-ref-state="resting"] .note'):
260
+ st.string = 'The name appears on every document in the project.'
261
+
262
+ elif key == 'settings-rows':
263
+ for s in soup.select('.setting'):
264
+ s['class'] = ['setting', 'f-setting']
265
+ for note in soup.select('[data-ref-state="disabled"] .note'):
266
+ replace_with(note, f'<p class="f-status-line f-locked">{icon("lock", 14)}<span>Only the workspace owner can change these settings.</span></p>')
267
+ for sd in soup.select('.settings-disabled'):
268
+ sd['class'] = ['settings-disabled', 'f-settings-disabled']
269
+
270
+ elif key == 'form-error-summary':
271
+ errors = states['errors']
272
+ notice = errors.select_one('.notice.error')
273
+ notice['class'] = ['notice', 'error', 'f-error-summary']
274
+ prepend_html(notice, icon('warning', 18))
275
+ ul = notice.select_one('ul')
276
+ del ul['style']
277
+ stack = errors.select_one('.stack[style]')
278
+ replace_contents(stack, f'''<label class="field f-field is-invalid">Project name<input aria-invalid="true" class="bevel-input" id="e-name" value="" placeholder="e.g. Field notes"><small class="f-field-error">{icon("warning", 13)}A project name is required.</small></label>
279
+ <label class="field f-field is-invalid">Invite a collaborator<input aria-invalid="true" class="bevel-input" id="e-email" value="mira@"><small class="f-field-error">{icon("warning", 13)}Enter an address like mira@example.test.</small></label>''')
280
+ del stack['style']
281
+ for act in soup.select('.actions'):
282
+ replace_contents(act, button('Cancel') + button('Create project', None, 'bevel-button--primary'))
283
+ corrected = states['corrected']
284
+ notice = corrected.select_one('.notice.success')
285
+ notice['class'] = ['notice', 'success', 'f-error-summary']
286
+ replace_contents(notice, f'{icon("check", 18)}<div><strong>Everything looks good.</strong><span>Both fields are complete. Your project is ready to create.</span></div>')
287
+ stack = corrected.select_one('.stack[style]')
288
+ del stack['style']
289
+ stack['class'] = ['stack', 'f-form-stack']
290
+ for lb in stack.select('label.field'): lb['class'] = ['field', 'f-field']
291
+
292
+ elif key == 'split-button':
293
+ for group in soup.select('.p-split-button'):
294
+ main = group.select_one('.bevel-button')
295
+ main['class'] = ['bevel-button']
296
+ prepend_html(main.select_one('.bevel-button__label'), icon('doc', 15))
297
+ caret = group.select_one('.p-icon-button')
298
+ caret['class'] = ['f-split-caret']
299
+ svg = caret.select_one('svg')
300
+ replace_with(svg, icon('chevron', 15))
301
+ sec = group.find_parent('section')
302
+ if 'open' in sec['data-ref-state']:
303
+ caret['aria-expanded'] = 'true'
304
+ for menu in soup.select('.p-menu'):
305
+ del menu['style']
306
+ menu['class'] = ['p-menu', 'f-split-menu']
307
+ names = ['pdf', 'html', 'markdown']
308
+ for i, item in enumerate(menu.select('button')):
309
+ svg = item.select_one('svg')
310
+ replace_with(svg, icon('doc', 16))
311
+ if item.select_one('svg + span + svg'):
312
+ replace_with(item.select_one('svg + span + svg'), icon('check', 14))
313
+ item['aria-checked'] = 'true'
314
+ for lab in soup.select('.r-split-label'):
315
+ text = lab.get_text()
316
+ lab.string = ''
317
+ append_html(lab, f'<span class="f-variant">{"A" if "dark" in text else "B"}</span>{text}')
318
+ for p in soup.select('.p-panel > p.p-small[style]'):
319
+ del p['style']
320
+ p['class'] = ['p-small', 'p-muted', 'f-split-note']
321
+
322
+ elif key == 'sidebar-nav':
323
+ for rail in soup.select('.p-sidebar'):
324
+ compact = 'compact' in rail.get('class', [])
325
+ btn = rail.select_one('.r-collapse')
326
+ replace_with(btn, icon_button('collapse-right' if compact else 'collapse-left', 'Expand sidebar' if compact else 'Collapse sidebar', 'r-collapse', 15))
327
+ av = rail.select_one('.r-avatar')
328
+ av['class'] = ['f-avatar']
329
+ av['data-who'] = 'JP'
330
+ foot = rail.select_one('footer.r-identity')
331
+ append_html(foot, f'<button type="button" class="f-identity-more" aria-label="Account menu">{icon("chevron", 13)}</button>')
332
+ for item in soup.select('.p-nav-item'):
333
+ small = item.select_one('small')
334
+ if small: small['class'] = ['f-count']
335
+ for content in soup.select('.p-app-content'):
336
+ p = content.select_one('p[style]')
337
+ if p: del p['style']
338
+ for row in content.select('.p-document-row'):
339
+ row['class'] = ['p-document-row', 'f-doc-row']
340
+ svg = row.select_one(':scope > svg:last-child')
341
+ if svg: replace_with(svg, icon('chevron-right', 13))
342
+
343
+ elif key == 'app-toolbar':
344
+ for toolbar in soup.select('.toolbar'):
345
+ toolbar['class'] = ['toolbar', 'f-toolbar']
346
+ for sep in toolbar.select('.toolbar-sep'): sep.decompose()
347
+ btns = toolbar.select('button')
348
+ groups = {'New note': ('plus', 'f-toolbar-new'), 'List view': ('list', ''), 'Grid view': ('board', ''), 'More toolbar actions': ('more', 'f-toolbar-more')}
349
+ new_html = ''
350
+ seg = ''
351
+ for b in btns:
352
+ label = b.get('aria-label')
353
+ name, extra = groups[label]
354
+ pressed = b.get('aria-pressed')
355
+ html = icon_button(name, label, extra, 17, pressed)
356
+ if label in ('List view', 'Grid view'):
357
+ seg += html
358
+ elif label == 'New note':
359
+ new_html += html
360
+ search = toolbar.select_one('input')
361
+ search_html = f'<label class="f-toolbar-search">{icon("search", 15)}<input type="search" class="bevel-input bevel-input--small" placeholder="Search notes" aria-label="Search notes"><kbd>/</kbd></label>'
362
+ expanded = 'true' if toolbar.find_parent('section')['data-ref-state'] == 'overflow' else 'false'
363
+ more_html = icon_button('more', 'More actions', 'f-toolbar-more', 17).replace('<button ', f'<button aria-expanded="{expanded}" ')
364
+ replace_contents(toolbar, f'{new_html}<span class="f-toolbar-sep"></span><div class="bevel-seg f-toolbar-seg" role="group" aria-label="Layout">{seg}</div><span class="f-toolbar-sep"></span>{button("Filter", "filter")}<span class="spacer"></span>{search_html}{more_html}')
365
+ for row in soup.select('.listrow'):
366
+ glyph = row.select_one(':scope > span')
367
+ if glyph: replace_with(glyph, f'<span class="f-file-icon">{icon("doc", 18)}</span>')
368
+ for menu in soup.select('[data-ref-state="overflow"] .menu'):
369
+ menu['class'] = ['menu', 'f-overflow-menu']
370
+ icons = ['sort', 'archive', 'share']
371
+ for i, item in enumerate(menu.select('button')):
372
+ text = item.get_text()
373
+ replace_contents(item, f'<span>{icon(icons[i], 15)}</span>{text}')
374
+ wrap = menu.parent
375
+ if wrap.get('style'):
376
+ del wrap['style']
377
+ wrap['class'] = ['f-overflow-anchor']
378
+
379
+ elif key == 'breadcrumbs':
380
+ for nav in soup.select('.breadcrumb'):
381
+ nav['class'] = ['breadcrumb', 'f-breadcrumb']
382
+ links = nav.select('a')
383
+ for sep in nav.select('span[aria-hidden]'):
384
+ replace_contents(sep, icon('chevron-right', 12))
385
+ if len(links) > 3:
386
+ for a in links[1:-1]:
387
+ a['class'] = ['f-crumb-mid']
388
+ sep = a.find_next_sibling('span')
389
+ if sep: sep['class'] = ['f-crumb-mid']
390
+ links[0].find_next_sibling('span').insert_after(frag(f'<button type="button" class="f-crumb-more" aria-label="Show full path">…</button><span aria-hidden="true" class="f-crumb-more-sep">{icon("chevron-right", 12)}</span>'))
391
+ for row in soup.select('.rule.row'):
392
+ av = row.select_one('.avatar')
393
+ av['class'] = ['f-avatar']
394
+ av['data-who'] = 'MC'
395
+
396
+ elif key == 'pagination':
397
+ for nav in soup.select('.pagination'):
398
+ nav['class'] = ['pagination', 'f-pagination']
399
+ for b in nav.select('button'):
400
+ lab = b.get('aria-label', '')
401
+ if lab == 'Previous page': replace_contents(b, icon('chevron-left', 15))
402
+ elif lab == 'Next page': replace_contents(b, icon('chevron-right', 15))
403
+ for gap in nav.select('span.muted'):
404
+ del gap['style']
405
+ gap['class'] = ['f-page-gap']
406
+ for note in soup.select('.note'):
407
+ note.string = note.get_text().split('·')[0].strip()
408
+ for row in soup.select('.rule.row.between.wrap'):
409
+ row['class'] = ['rule', 'row', 'between', 'wrap', 'f-pager-row']
410
+
411
+ elif key == 'tree-view':
412
+ for summ in soup.select('.p-tree summary'):
413
+ svg = summ.select_one('svg')
414
+ replace_with(svg, f'<i class="f-tree-caret">{icon("chevron-right", 12)}</i>{icon("folder", 16)}')
415
+ for leaf in soup.select('.p-tree-leaf'):
416
+ svg = leaf.select_one('svg')
417
+ replace_with(svg, icon('doc', 15))
418
+ for badge in soup.select('.p-tree .p-badge'):
419
+ if badge.get_text().strip() == 'Open': badge['class'] = ['p-badge', 'f-tree-open']
420
+ for empty in soup.select('.p-tree-empty'):
421
+ replace_contents(empty, f'{icon("folder", 14)}No documents here yet.')
422
+ for head in soup.select('.p-tree-head .p-badge'):
423
+ head.string = '12 files · 4 folders'
424
+
425
+ elif key == 'wizard-shell':
426
+ for st, sec in states.items():
427
+ steps = sec.select('.wizard-step')
428
+ for i, step in enumerate(steps):
429
+ b = step.select_one('b')
430
+ small = step.select_one('small')
431
+ label = small.get_text().strip() if small else ''
432
+ state = 'done' if label == 'Complete' else 'current' if label == 'Current step' else 'next'
433
+ step['data-step'] = state
434
+ step['class'] = ['wizard-step'] + (['current'] if state == 'current' else [])
435
+ if state == 'done':
436
+ replace_contents(b, icon('check', 13))
437
+ b['aria-label'] = f'Step {i+1} complete'
438
+ if small:
439
+ del small['style']
440
+ small['class'] = ['f-step-note']
441
+ wz = sec.select_one('.wizard')
442
+ wz['class'] = ['wizard', 'f-wizard']
443
+ for p in soup.select('.wizard p.muted[style]'):
444
+ del p['style']
445
+ p['class'] = ['muted', 'f-wizard-lede']
446
+ for sel in soup.select('.wizard div[style="margin-top:16px"]'):
447
+ del sel['style']
448
+ replace_with(sel, f'<label class="field" style="margin-top:16px">Role{str(sel.select_one(".bevel-select"))}</label>')
449
+ err = states['error'].select_one('p.bad')
450
+ if err: replace_with(err, f'<p class="f-field-error">{icon("warning", 13)}Enter a complete email address, like mira@example.test.</p>')
451
+ for badge in states['complete'].select('.badge.good'):
452
+ replace_contents(badge, f'{icon("check", 11)}Setup complete')
453
+ well = states['complete'].select_one('.well')
454
+ if well:
455
+ replace_with(well, f'<div class="f-archive-card">{icon("folder", 22)}<div><strong>Field notes</strong><small>Northstar Studio · Private project · 1 collaborator invited</small></div><span class="badge good">Ready</span></div>')
456
+
457
+ elif key == 'toast-stack':
458
+ for stage in soup.select('.p-toast-stage'):
459
+ stage['class'] = ['p-toast-stage', 'f-toast-stage']
460
+ for toast in soup.select('.p-toast'):
461
+ toast['class'] = toast.get('class', []) + ['f-toast']
462
+ close = toast.select_one('.p-icon-button')
463
+ close['class'] = ['f-toast-close']
464
+ replace_contents(close, icon('close', 14))
465
+ action = toast.select_one('.p-text-action')
466
+ if action:
467
+ text = action.get_text()
468
+ replace_with(action, button(text, 'undo' if text == 'Undo' else 'undo', 'bevel-button--small f-toast-action'))
469
+ ic = toast.select_one('.p-toast-icon svg')
470
+ kind = 'success' if 'success' in toast['class'] else 'error' if 'error' in toast['class'] else 'note'
471
+ replace_with(ic, icon({'success': 'check', 'error': 'warning', 'note': 'archive'}[kind], 16))
472
+
473
+ elif key == 'persistent-action-banner':
474
+ for banner in soup.select('.p-banner'):
475
+ banner['class'] = banner.get('class', []) + ['f-banner']
476
+ svg = banner.select_one(':scope > span > svg')
477
+ resolved = 'resolved' in banner['class']
478
+ replace_with(svg, icon('check' if resolved else 'offline', 20))
479
+ btn = banner.select_one('.bevel-button')
480
+ if resolved:
481
+ replace_with(btn, f'<span class="f-banner-actions">{button("Dismiss", None, "bevel-button--small")}</span>')
482
+ else:
483
+ replace_with(btn, f'<span class="f-banner-actions">{button("Retry connection", "undo", "bevel-button--small")}<span class="f-banner-meta">Last synced 10:42 · 3 changes waiting</span></span>')
484
+ for panel in soup.select('.p-panel[style]'):
485
+ del panel['style']
486
+ panel['class'] = ['p-panel', 'f-banner-page']
487
+
488
+ elif key == 'save-status-indicator':
489
+ chip = soup.select_one('.r-saved-chip')
490
+ replace_contents(chip, f'{icon("check", 12)}Saved')
491
+
492
+ elif key == 'code-block':
493
+ for shell in soup.select('.code-shell'):
494
+ shell['class'] = ['code-shell', 'f-code']
495
+ head = shell.select_one('.code-head')
496
+ wrapped = 'wrap-code' in shell.select_one('.code-lines').get('class', [])
497
+ replace_contents(head, f'<span class="f-code-name">{icon("doc", 15)}field-note.html</span><span class="badge">HTML</span><span class="f-code-meta">4 lines</span><span class="spacer"></span>'
498
+ + button('Wrap', 'wrap', 'bevel-button--small', f'aria-pressed="{"true" if wrapped else "false"}"') + button('Copy', 'copy', 'bevel-button--small'))
499
+ ol = shell.select_one('.code-lines')
500
+ ol['class'] = ['code-lines', 'f-code-lines'] + (['wrap-code'] if wrapped else [])
501
+ replace_contents(ol, ''.join(f'<li><code>{line}</code></li>' for line in CODE_LINES))
502
+ for note in soup.select('.note'):
503
+ note.string = 'Copy takes the whole file. Wrap keeps long attribute lines in view.'
504
+
505
+ elif key == 'diff-view':
506
+ for diff in soup.select('.p-diff'):
507
+ diff['class'] = ['p-diff', 'f-diff']
508
+ head = diff.select_one(':scope > header')
509
+ svg = head.select_one('svg')
510
+ if svg: replace_with(svg, icon('doc', 15))
511
+ btn = head.select_one('.bevel-button')
512
+ split = 'Unified' in btn.get_text()
513
+ replace_with(btn, f'<div class="bevel-seg f-diff-seg" role="group" aria-label="Layout">{button("Unified", None, "", "aria-pressed=" + ("false" if split else "true"))}{button("Split", None, "", "aria-pressed=" + ("true" if split else "false"))}</div>')
514
+ for line in soup.select('.p-diff-line'):
515
+ line['class'] = line.get('class', []) + ['f-diff-line']
516
+ split = states['split'].select_one('.p-diff-split')
517
+ replace_with(split, '''<div class="f-diff-split"><section><h3>Before <small>field-note.html @ 1,4</small></h3>
518
+ <div class="p-diff-line f-diff-line"><span>1</span><span> </span><code>&lt;article class="field-note"&gt;</code></div>
519
+ <div class="p-diff-line f-diff-line removed"><span>2</span><span>−</span><code>&lt;h1&gt;A place for <mark>ideas</mark>&lt;/h1&gt;</code></div>
520
+ <div class="p-diff-line f-diff-line removed"><span>3</span><span>−</span><code>&lt;p&gt;Keep <mark>everything</mark> close.&lt;/p&gt;</code></div>
521
+ <div class="p-diff-line f-diff-line"><span>4</span><span> </span><code>&lt;/article&gt;</code></div></section>
522
+ <section><h3>After <small>field-note.html @ 1,4</small></h3>
523
+ <div class="p-diff-line f-diff-line"><span>1</span><span> </span><code>&lt;article class="field-note"&gt;</code></div>
524
+ <div class="p-diff-line f-diff-line added"><span>2</span><span>+</span><code>&lt;h1&gt;A place for <mark>good work</mark>&lt;/h1&gt;</code></div>
525
+ <div class="p-diff-line f-diff-line added"><span>3</span><span>+</span><code>&lt;p&gt;Keep <mark>the useful things</mark> close.&lt;/p&gt;</code></div>
526
+ <div class="p-diff-line f-diff-line"><span>4</span><span> </span><code>&lt;/article&gt;</code></div></section></div>''')
527
+
528
+ elif key == 'activity-feed':
529
+ verbs = {'updated': 'pencil', 'joined': 'user', 'added': 'image', 'created': 'plus'}
530
+ for ev in soup.select('.event'):
531
+ ev['class'] = ['event', 'f-event']
532
+ av = ev.select_one('.avatar')
533
+ who = av.get_text().strip()
534
+ av['class'] = ['f-avatar']
535
+ av['data-who'] = who
536
+ p = ev.select_one('p')
537
+ time = ev.select_one('time')
538
+ detail = ev.select_one('p.muted')
539
+ verb = [t for t in p.find_all(string=True, recursive=False) if t.strip()]
540
+ verb_word = verb[0].strip() if verb else ''
541
+ head = f'<div class="f-event-head">{str(p)}<time>{time.get_text()}</time></div>'
542
+ body = f'<p class="f-event-detail">{detail.get_text()}</p>' if detail else ''
543
+ replace_contents(ev, f'{str(av)}<div class="f-event-body">{head}{body}</div>')
544
+ ev['data-verb'] = verbs.get(verb_word, 'doc')
545
+ for eb in soup.select('.specimen > .eyebrow'):
546
+ eb['class'] = ['eyebrow', 'f-day']
547
+ for btn in soup.select('[data-ref-state="recent"] .bevel-button--quiet'):
548
+ replace_with(btn, button('Show yesterday', 'chevron', 'bevel-button--small f-show-more'))
549
+
550
+ elif key == 'stat-tiles':
551
+ sparks = ['0,36 26,29 53,33 80,16 106,20 133,5 160,10', '0,30 26,30 53,22 80,22 106,14 133,14 160,6', '0,20 26,28 53,24 80,14 106,18 133,8 160,4']
552
+ deltas = [('good', 'up', '18% this week'), ('good', 'up', '3 this week'), ('bad', 'up', '2 more than last week')]
553
+ for st, sec in states.items():
554
+ tiles = sec.select('.stat')
555
+ for i, tile in enumerate(tiles):
556
+ small = tile.select_one('small')
557
+ tone, arrow, text = deltas[i]
558
+ if st == 'default':
559
+ replace_with(small, f'<small class="f-delta {tone}">{icon("arrow-" + arrow, 12)}{text}</small>')
560
+ svg = tile.select_one('svg.sparkline')
561
+ svg['viewBox'] = '0 0 160 42'
562
+ replace_contents(svg, f'<polyline fill="none" points="{sparks[i]}" stroke="currentColor" stroke-width="2"/><circle cx="160" cy="{sparks[i].split()[-1].split(",")[1]}" r="3" fill="currentColor"/><line stroke="var(--bevel-line)" x1="0" x2="160" y1="41" y2="41"/>')
563
+ else:
564
+ replace_with(small, '<small class="f-delta">No activity yet</small>')
565
+ sp = tile.select_one('.sparkline')
566
+ replace_with(sp, '<svg class="sparkline f-spark-flat" viewBox="0 0 160 42" aria-hidden="true"><line stroke="var(--bevel-line-2)" stroke-dasharray="2 4" x1="0" x2="160" y1="30" y2="30"/></svg>')
567
+ tile.select_one('.eyebrow')['class'] = ['eyebrow', 'f-stat-label']
568
+ for row in soup.select('.row.between.rule'):
569
+ row['class'] = ['row', 'between', 'rule', 'f-stat-foot']
570
+ for p in soup.select('[data-ref-state="zero"] .specimen > header p'):
571
+ p.string = 'Nothing to count yet. Numbers appear after the first document.'
572
+
573
+ elif key == 'avatar-group':
574
+ for stack in soup.select('.p-avatar-stack'):
575
+ stack['class'] = ['p-avatar-stack', 'f-avatar-stack']
576
+ for av in stack.select('.p-avatar'):
577
+ text = av.get_text().strip()
578
+ if text.startswith('+'):
579
+ av['class'] = ['f-avatar', 'f-avatar-more']
580
+ else:
581
+ av['class'] = ['f-avatar']
582
+ av['data-who'] = text
583
+ for lst in soup.select('.p-avatar-list'):
584
+ lst['class'] = ['p-avatar-list', 'f-avatar-list']
585
+ for row in lst.select('.p-row'):
586
+ av = row.select_one('.p-avatar')
587
+ av['class'] = ['f-avatar']
588
+ av['data-who'] = av.get_text().strip()
589
+ if av['data-who'] == 'MC':
590
+ row.select_one('p').string = ''
591
+ append_html(row.select_one('p'), '<i class="f-presence-dot"></i>Online now · Designer')
592
+ append_html(lst, f'<div class="p-row f-avatar-list-more"><span class="f-avatar f-avatar-more">+3</span><div><strong class="p-small">Jules Park, Priya Nair and Tomas Berg</strong><p class="p-small p-muted">Owner, Researcher, Producer</p></div></div>')
593
+ for p in soup.select('.p-between p[style]'):
594
+ del p['style']
595
+
596
+ elif key == 'small-charts':
597
+ for chart in soup.select('svg.chart'):
598
+ chart['class'] = ['chart', 'f-chart']
599
+ spark_state = states['spark']
600
+ row = spark_state.select_one('.row[style]')
601
+ replace_with(row, '''<div class="f-spark-row"><div class="f-spark-figure"><div class="stat-value">162</div><small class="f-delta good">''' + icon('arrow-up', 12) + '''18% this week</small></div>
602
+ <div class="f-spark-body"><svg aria-label="Views by day, rising through the week" class="sparkline f-sparkline" viewBox="0 0 160 42" preserveAspectRatio="none"><polyline fill="none" points="0,36 26,29 53,33 80,16 106,20 133,5 160,10" stroke="currentColor" stroke-width="2"/><circle cx="133" cy="5" r="3" fill="currentColor"/><line stroke="var(--bevel-line)" x1="0" x2="160" y1="41" y2="41"/></svg>
603
+ <div class="f-spark-days"><span><b>12</b>Mon</span><span><b>18</b>Tue</span><span><b>14</b>Wed</span><span><b>28</b>Thu</span><span><b>24</b>Fri</span><span class="is-peak"><b>35</b>Sat</span><span><b>31</b>Sun</span></div></div></div>''')
604
+ for r in spark_state.select('.rule.row.between'):
605
+ if 'MON 12' in r.get_text(): r.decompose()
606
+ for legend in soup.select('.rule.row.between'):
607
+ sw = legend.select_one('span[style]')
608
+ if sw: replace_with(sw, '<i class="f-swatch"></i>')
609
+ legend['class'] = ['rule', 'row', 'between', 'f-chart-legend']
610
+
611
+ elif key == 'filter-builder':
612
+ for row in soup.select('.filter-row'):
613
+ row['class'] = ['filter-row', 'f-filter-row']
614
+ word = row.select_one(':scope > span')
615
+ word['class'] = ['f-rule-word']
616
+ rm = row.select_one('.icon-button')
617
+ replace_with(rm, icon_button('close', 'Remove rule', 'f-rule-remove', 14))
618
+ for act in soup.select('.actions'):
619
+ add = act.select_one('[data-add-rule]')
620
+ replace_contents(add, icon('plus', 14) + '<span class="bevel-button__label">Add rule</span>')
621
+ set_label(act.select_one('.bevel-button--primary'), 'Apply filters')
622
+ for p in soup.select('[data-ref-state="empty"] .filters p'):
623
+ replace_with(p, f'<div class="f-empty-rules">{icon("filter", 18)}<span>No conditions yet. Start with a field, then a comparison.</span></div>')
624
+ for eb in soup.select('.rule > .eyebrow'):
625
+ eb['class'] = ['eyebrow', 'f-result-label']
626
+
627
+ elif key == 'column-manager':
628
+ for item in soup.select('.column-item'):
629
+ item['class'] = ['column-item', 'f-column-item']
630
+ grip = item.select_one(':scope > span.muted')
631
+ replace_with(grip, f'<span class="f-grip" aria-hidden="true">{icon("grip", 14)}</span>')
632
+ for b in item.select('.bevel-button'):
633
+ lab = b.get('aria-label', '')
634
+ b['class'] = ['f-icon-button', 'f-order-btn']
635
+ replace_contents(b, icon('chevron-up' if 'up' in lab else 'chevron-down', 14))
636
+ n = item.select_one('span.mono')
637
+ if n:
638
+ del n['style']
639
+ n['class'] = ['f-order-n']
640
+ for st, sec in states.items():
641
+ if st == 'reordered':
642
+ items = sec.select('.f-column-item')
643
+ items[1]['class'].append('is-moved')
644
+ n = items[1].select_one('.f-order-n')
645
+ replace_with(n, '<span class="f-order-n is-moved">2<i>moved</i></span>')
646
+ wrap = sec.select_one('.panel[style]')
647
+ del wrap['style']
648
+ wrap['class'] = ['panel', 'f-column-panel']
649
+ head = wrap.select_one('.pad[style]')
650
+ del head['style']
651
+ head['class'] = ['pad', 'f-column-head']
652
+ foot = wrap.select_one(':scope > .pad:last-child')
653
+ foot['class'] = ['pad', 'f-column-foot']
654
+ replace_contents(foot, f'{icon("lock", 13)}<small>Invoice stays pinned as the record identifier.</small><span class="spacer"></span><small class="f-order-summary">{"4 of 5 shown" }</small>')
655
+
656
+ elif key == 'editable-grid-cells':
657
+ for st, sec in states.items():
658
+ scroll = sec.select_one('.table-scroll')
659
+ if st == 'idle':
660
+ cell = '<td class="numeric f-cell is-editable"><button type="button" class="f-cell-button"><span>$3,200.00</span>' + icon('pencil', 12) + '</button></td>'
661
+ elif st == 'editing':
662
+ cell = ('<td class="numeric f-cell is-editing"><span class="f-cell-editor"><span class="f-cell-prefix">$</span><input aria-label="Amount for INV-0432" class="f-cell-input" value="3200.00">'
663
+ + icon_button('check', 'Save amount', 'f-cell-ok', 13) + icon_button('close', 'Cancel', 'f-cell-cancel', 13) + '</span></td>')
664
+ else:
665
+ cell = ('<td class="numeric f-cell is-editing is-invalid"><span class="f-cell-editor"><span class="f-cell-prefix">$</span><input aria-invalid="true" aria-label="Amount for INV-0432" class="f-cell-input" value="three thousand">'
666
+ + icon_button('check', 'Save amount', 'f-cell-ok', 13) + icon_button('close', 'Cancel', 'f-cell-cancel', 13) + f'</span><small class="f-cell-error">{icon("warning", 12)}Enter a number, like 3200</small></td>')
667
+ replace_with(scroll, invoice_table(cell).replace('<table>', '<table class="f-grid-table">'))
668
+ rows = sec.select('tbody tr')
669
+ if st != 'idle': rows[1]['class'] = ['is-editing-row']
670
+ act = sec.select_one('.actions')
671
+ if act:
672
+ replace_with(act, f'<p class="note f-edit-note">{icon("pencil", 12)}Editing INV-0432 · Amount. <kbd>↵</kbd> saves, <kbd>esc</kbd> cancels.</p>')
673
+ for note in soup.select('[data-ref-state="idle"] .note'):
674
+ replace_contents(note, f'{icon("pencil", 12)}Amounts can be edited in place. Choose a value to change it.')
675
+ note['class'] = ['note', 'f-edit-note']
676
+
677
+ elif key == 'split-panes':
678
+ for sp in soup.select('.p-split-panes'):
679
+ sp['class'] = ['p-split-panes', 'f-split-panes']
680
+ div = sp.select_one('.p-pane-divider')
681
+ replace_contents(div, icon('grip', 14))
682
+ for item in sp.select('.p-pane-item'):
683
+ svg = item.select_one('svg')
684
+ replace_with(svg, icon('doc', 15))
685
+ pre = sp.select_one('pre')
686
+ if pre:
687
+ replace_contents(pre, '<span class="tk-tag">&lt;article&gt;</span>\n <span class="tk-tag">&lt;h1&gt;</span>Field notes<span class="tk-tag">&lt;/h1&gt;</span>\n <span class="tk-tag">&lt;p&gt;</span>Keep the useful\n things close.<span class="tk-tag">&lt;/p&gt;</span>\n<span class="tk-tag">&lt;/article&gt;</span>')
688
+ for lab in soup.select('.p-pane-adjust'):
689
+ state = lab.find_parent('section')['data-ref-state']
690
+ replace_with(lab, f'<p class="p-pane-adjust f-pane-caption">{icon("grip", 12)}{"List 32% · minimum 25% · drag the divider" if state == "list-detail" else "Source 46% · minimum 25% · drag the divider"}</p>')
691
+ for sp in states['list-detail'].select('.f-split-panes'):
692
+ sp['style'] = '--split:32%'
693
+ for row in soup.select('.p-pane-detail .p-row[style]'):
694
+ del row['style']
695
+ row['class'] = ['p-row', 'f-pane-byline']
696
+ av = row.select_one('.p-avatar')
697
+ av['class'] = ['f-avatar', 'small']
698
+ av['data-who'] = 'MC'
699
+
700
+ elif key == 'rich-text-toolbar':
701
+ names = {'link': 'link', 'unorderedList': 'list', 'undo': 'undo'}
702
+ for tb in soup.select('.richclay-toolbar'):
703
+ tb['class'] = ['richclay-toolbar', 'f-rt-toolbar']
704
+ block = tb.select_one('[data-richclay-control="blockMenu"]')
705
+ replace_contents(block, f'<span>Paragraph</span>{icon("chevron", 13)}')
706
+ for ctrl, name in names.items():
707
+ b = tb.select_one(f'[data-richclay-control="{ctrl}"]')
708
+ if b: replace_contents(b, icon(name, 15))
709
+ for menu in soup.select('.richclay-menu'):
710
+ menu['class'] = ['richclay-menu', 'f-rt-menu']
711
+ for item in menu.select('.richclay-menu-item'):
712
+ text = item.get_text()
713
+ cls = {'Paragraph': 'f-rt-p', 'Heading 1': 'f-rt-h1', 'Heading 2': 'f-rt-h2', 'Quote': 'f-rt-q'}[text]
714
+ replace_contents(item, f'<span class="{cls}">{text}</span>' + (icon('check', 13) if item.get('aria-checked') == 'true' else ''))
715
+ for ed in soup.select('.richclay-editor'):
716
+ ed['class'] = ['richclay-editor', 'f-rt-editor']
717
+ link_state = states['link']
718
+ dlg = link_state.select_one('.richclay-link-dialog')
719
+ editor = link_state.select_one('.f-rt-editor')
720
+ a = editor.select_one('a')
721
+ a['class'] = ['is-editing']
722
+ dlg.extract()
723
+ dlg['class'] = ['richclay-dialog', 'f-rt-link']
724
+ replace_contents(dlg, f'<div class="f-rt-link-head">{icon("link", 14)}<span class="eyebrow">Edit link</span><span class="f-keys"><kbd>esc</kbd> close</span></div>'
725
+ '<label class="field">URL<input class="bevel-input bevel-input--small" type="text" value="https://example.test/field-guide"></label>'
726
+ '<label class="field">Text<input class="bevel-input bevel-input--small" type="text" value="Read the field guide"></label>'
727
+ f'<div class="f-rt-link-actions">{button("Remove link", "unlink", "bevel-button--small bevel-button--quiet")}<span class="spacer"></span>{button("Cancel", None, "bevel-button--small")}{button("Apply", None, "bevel-button--small bevel-button--primary")}</div>')
728
+ a.insert_after(dlg)
729
+ floating = states['floating']
730
+ wrap = floating.select_one('.specimen > div[style]')
731
+ editor = wrap.select_one('.f-rt-editor')
732
+ editor.extract()
733
+ replace_with(wrap, str(editor))
734
+ editor = floating.select_one('.f-rt-editor')
735
+ editor['class'].append('is-floating')
736
+ p = editor.select_one('p')
737
+ mark = p.select_one('mark')
738
+ del mark['style']
739
+ mark['class'] = ['f-selection']
740
+ p.insert_before(frag(f'<div class="richclay-float f-rt-float" role="toolbar" aria-label="Selection formatting">'
741
+ f'<button class="richclay-button" aria-pressed="true" aria-label="Bold"><b>B</b></button><button class="richclay-button" aria-pressed="false" aria-label="Italic"><i>I</i></button>'
742
+ f'<button class="richclay-button" aria-pressed="false" aria-label="Underline"><u>U</u></button><button class="richclay-button" aria-pressed="false" aria-label="Strikethrough"><s>S</s></button>'
743
+ f'<span class="richclay-separator"></span><button class="richclay-button" aria-label="Edit link">{icon("link", 15)}</button><button class="richclay-button" aria-label="Highlight">{icon("highlight", 15)}</button></div>'))
744
+ for note in soup.select('.specimen > p.note'):
745
+ note.decompose()
746
+ for hp in soup.select('.specimen > header p'):
747
+ hp.string = 'Richclay formatting, in the Bevel material.'
748
+
749
+ elif key == 'context-hypercms':
750
+ for close in soup.select('[data-hcms-action="close"]'):
751
+ replace_with(close, icon_button('close', 'Close inspector', 'f-hcms-close', 15))
752
+ for err in soup.select('.hcms-shell-error'):
753
+ err['class'] = err.get('class', []) + ['f-error-summary']
754
+ if not err.has_attr('hidden'):
755
+ text = err.get_text()
756
+ replace_contents(err, f'{icon("warning", 16)}<div><strong>{text}</strong></div>')
757
+ for e in soup.select('.hcms-error:not([hidden])'):
758
+ if e.get_text().strip():
759
+ text = e.get_text()
760
+ replace_contents(e, f'{icon("warning", 12)}{text}')
761
+ e['class'] = ['hcms-error', 'f-field-error']
762
+ for card in soup.select('.hcms-card'):
763
+ handle = card.select_one('.hcms-drag-handle')
764
+ replace_contents(handle, icon('grip', 13))
765
+ for up in soup.select('.hcms-upload .row'):
766
+ up['class'] = ['row', 'f-hcms-upload']
767
+ for rule in soup.select('.hcms-preview .rule'):
768
+ replace_contents(rule, f'<div class="f-hcms-byline">{avatar("MC", "small")}<small>Mira Chen · edited 10:42</small></div>')
769
+ for h3 in soup.select('.hcms-array-title'):
770
+ small = h3.select_one('small')
771
+ if small: small['class'] = ['f-count']
772
+
773
+ elif key == 'context-quickcrop':
774
+ for modal in soup.select('.p-modal'):
775
+ modal['class'] = ['p-modal', 'f-crop-modal']
776
+ del modal['style']
777
+ corner = modal.select_one('.p-corner')
778
+ replace_with(corner, f'<button aria-label="Close dialog" class="micromodal__close" type="button">{CLOSE_TOKEN}</button>')
779
+ head = modal.select_one('.p-between[style]')
780
+ del head['style']
781
+ head['class'] = ['p-between', 'f-crop-head']
782
+ h2 = head.select_one('h2')
783
+ del h2['style']
784
+ for act in soup.select('.p-crop-actions'):
785
+ state = act.find_parent('section')['data-ref-state']
786
+ seg = act.select_one('.p-row')
787
+ replace_with(seg, f'<div class="bevel-seg f-crop-seg" role="group" aria-label="Aspect ratio">{button("1:1", None, "", "aria-pressed=" + ("true" if state == "locked" else "false"))}{button("Free", None, "", "aria-pressed=" + ("true" if state == "free" else "false"))}</div>')
788
+ note = act.select_one('.p-small')
789
+ replace_contents(note, (icon('lock', 12) + 'Aspect ratio locked') if state == 'locked' else (icon('expand', 12) + 'Any proportion'))
790
+ note['class'] = ['p-small', 'p-muted', 'f-crop-note']
791
+ bar = act.select_one('.qc-bar')
792
+ replace_contents(bar, button('Reset', 'undo', 'bevel-button--small') + button('Use image', None, 'bevel-button--small bevel-button--primary'))
793
+ return soup
794
+
795
+
796
+ def shell(name, key, content):
797
+ return frag(f'<!doctype html><html lang="en" class="light"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>{name} · Fable</title></head><body data-component="{key}">{content}</body></html>')
798
+
799
+
800
+ SAVED_PAGE = '''<section data-ref-state="saved"><main class="r-saved-page f-saved-page"><header><span class="eyebrow">Northstar Studio / Field notes</span><span class="f-saved-meta"><span class="badge">Draft</span><span class="mono muted">Edited 10:42</span></span></header><article><span class="eyebrow">Field note / 001</span><h1>A place for good work.</h1><p>Keep the useful things close. Field notes brings the documents, decisions and small details of a project into one place.</p><p>A little room to think. A clear place to begin.</p></article><span class="r-saved-chip" role="status">Saved</span></main></section>'''
801
+
802
+
803
+ def finish(soup, component, source):
804
+ for css in soup.select('link[rel="stylesheet"]'):
805
+ css.decompose()
806
+ for script in soup.select('script'):
807
+ script.decompose()
808
+ for i, s in enumerate(soup.select('[data-ref-state]')):
809
+ s['inert'] = ''
810
+ if i: s['hidden'] = ''
811
+ elif s.has_attr('hidden'): del s['hidden']
812
+ soup.html['class'] = ['light']
813
+ if soup.html.has_attr('data-theme'): del soup.html['data-theme']
814
+ soup.body['data-component'] = component['id']
815
+ soup.body['data-reference-phase'] = 'static'
816
+ soup.body['data-selected-source'] = source
817
+ soup.body['data-seat'] = 'fable'
818
+ soup.title.string = f"{component['name']} · Fable · Bevel"
819
+ base = 'parent' if source == 'parent' else 'astra'
820
+ for href in ['../../baseline/bevel-fonts.css', '../../baseline/bevel.css', f'../base-{base}.css', '../fable.css']:
821
+ append_html(soup.head, f'<link rel="stylesheet" href="{href}">')
822
+ if component['id'] == 'kanban-board':
823
+ append_html(soup.head, '<link rel="stylesheet" href="../kanban.css">')
824
+ for old in soup.select('.r-static-note'):
825
+ old.decompose()
826
+ append_html(soup.body, '<footer class="r-static-note">Static UI reference · Fable seat · Use the preview controls to compare states.</footer><script src="../../fixtures/static-host.js"></script>')
827
+ html = str(soup)
828
+ for attr in ['viewBox', 'preserveAspectRatio', 'gradientUnits', 'markerWidth', 'markerHeight']:
829
+ html = html.replace(attr.lower() + '=', attr + '=')
830
+ return html.replace(CLOSE_TOKEN, CLOSE_SVG)
831
+
832
+
833
+ coverage = {'seat': 'fable', 'phase': 'static', 'components': [], 'contexts': []}
834
+ for c in MANIFEST['components'] + MANIFEST['contexts']:
835
+ key = c['id']
836
+ a = FEEDBACK.get(key)
837
+ picked = a['picked'][0] if a else None
838
+ source = 'parent' if picked in ('parent', 'iterate') or key == 'context-quickcrop' else 'astra'
839
+ if key == 'save-status-indicator':
840
+ soup = shell(c['name'], key, SAVED_PAGE)
841
+ elif key == 'kanban-board':
842
+ soup = shell(c['name'], key, ''.join(f'<section data-ref-state="{s}">{kanban(s)}</section>' for s in c['states']))
843
+ else:
844
+ soup = refine(frag((PARENT / f'{key}.html').read_text()), key)
845
+ (HERE / 'components' / f'{key}.html').write_text(finish(soup, c, source))
846
+ coverage['components' if a else 'contexts'].append({
847
+ 'id': key, 'states': c['states'], 'source': 'fable iteration' if picked == 'iterate' else f'{source} selection, Fable refinement',
848
+ 'file': f'components/{key}.html', 'behavior': 'static', 'feedback': a['comment'] if a else ''})
849
+ (HERE / 'coverage.json').write_text(json.dumps(coverage, indent=2) + '\n')
850
+
851
+ links = ''.join(f'<a href="components/{c["id"]}.html"><strong>{c["name"]}</strong><small>{c["family"]} · {len(c["states"])} states</small></a>' for c in MANIFEST['components'])
852
+ contexts = ''.join(f'<a href="components/{c["id"]}.html"><strong>{c["name"]}</strong><small>context · {len(c["states"])} states</small></a>' for c in MANIFEST['contexts'])
853
+ (HERE / 'index.html').write_text(f'''<!doctype html><html lang="en" class="light"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Fable seat · Bevel components</title>
854
+ <link rel="stylesheet" href="../baseline/bevel-fonts.css"><link rel="stylesheet" href="../baseline/bevel.css">
855
+ <style>body{{max-width:1040px;margin:40px auto;padding:24px;font:15px/1.5 var(--bevel-font-sans)}}.kicker{{font:11px var(--bevel-font-mono);letter-spacing:.16em;text-transform:uppercase;color:var(--bevel-brass);margin-bottom:12px}}h1{{font:400 42px/1.05 var(--bevel-font-serif);letter-spacing:-.02em;margin:0 0 12px}}p{{color:var(--bevel-ink-2);max-width:60ch;margin:0 0 28px}}h2{{font:400 22px var(--bevel-font-serif);margin:36px 0 12px}}nav{{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:1px;background:var(--bevel-line-2);border:1px solid var(--bevel-line-2)}}a{{display:block;padding:16px 18px;background:var(--bevel-surface);color:var(--bevel-ink);text-decoration:none}}a:hover{{background:var(--bevel-sunk)}}strong{{display:block;font-weight:500}}small{{display:block;color:var(--bevel-muted);font:11px var(--bevel-font-mono);margin-top:6px}}.foot{{margin-top:32px;font:11px var(--bevel-font-mono);color:var(--bevel-muted)}}</style>
856
+ <div class="kicker">Bevel · Fable seat · Static refinement</div><h1>Every component, one more pass.</h1><p>Thirty-three static components and two product contexts, built from the selected references and refined for proportion, contrast and detail. Open any component and use the preview controls to switch states and colour modes.</p>
857
+ <h2>Components</h2><nav>{links}</nav><h2>Contexts</h2><nav>{contexts}</nav><p class="foot">Regenerate with <code>python3 build.py</code>. Notes in IMPROVEMENTS.md.</p></html>''')
858
+ print(f"Fable: {len(coverage['components'])} components, {len(coverage['contexts'])} contexts, {sum(len(c['states']) for c in MANIFEST['components'])} component states.")