@aceshooting/lyra-ui 27.0.0 → 28.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 (397) hide show
  1. package/CHANGELOG.md +67 -63
  2. package/README.md +7 -3
  3. package/dist/cli/css-declarations.mjs +1 -0
  4. package/dist/cli/lyra-rename-ledger.mjs +1 -1
  5. package/dist/cli/migrate-wa.mjs +9 -4
  6. package/dist/cli/migration-contract.json +1 -1
  7. package/dist/cli/migration-renames.mjs +2 -2
  8. package/dist/cli/migration-theme-scopes.mjs +3 -0
  9. package/dist/cli/theme-scope-vocabulary.generated.mjs +1 -0
  10. package/dist/components/agent-tools/activity-feed/activity-feed.class.js +1 -1
  11. package/dist/components/agent-tools/agent-eval-dashboard/agent-eval-dashboard.class.js +1 -1
  12. package/dist/components/agent-tools/agent-question/agent-question.class.js +1 -1
  13. package/dist/components/agent-tools/agent-run/agent-run.class.js +1 -1
  14. package/dist/components/agent-tools/agent-trace/agent-trace.class.js +1 -1
  15. package/dist/components/agent-tools/approval-queue/approval-queue.class.js +1 -1
  16. package/dist/components/agent-tools/artifact-panel/artifact-panel.class.js +1 -1
  17. package/dist/components/agent-tools/background-runs/background-runs.class.js +1 -1
  18. package/dist/components/agent-tools/browser-frame/browser-frame.class.js +1 -1
  19. package/dist/components/agent-tools/budget-meter/budget-meter.class.js +1 -1
  20. package/dist/components/agent-tools/change-review/change-review.class.js +1 -1
  21. package/dist/components/agent-tools/commit-card/commit-card.class.js +1 -1
  22. package/dist/components/agent-tools/compare-panel/compare-panel.class.js +1 -1
  23. package/dist/components/agent-tools/confirm-bar/confirm-bar.class.js +1 -1
  24. package/dist/components/agent-tools/connector-manager/connector-manager.class.js +1 -1
  25. package/dist/components/agent-tools/context-inspector/context-inspector.class.js +1 -1
  26. package/dist/components/agent-tools/eval-dataset/eval-dataset.class.js +1 -1
  27. package/dist/components/agent-tools/eval-result/eval-result.class.js +1 -1
  28. package/dist/components/agent-tools/evaluation-run/evaluation-run.class.js +1 -1
  29. package/dist/components/agent-tools/mcp-app/mcp-app.class.js +1 -1
  30. package/dist/components/agent-tools/permission-grant/permission-grant.class.js +1 -1
  31. package/dist/components/agent-tools/permission-rules/permission-rules.class.js +1 -1
  32. package/dist/components/agent-tools/policy-summary/policy-summary.class.js +1 -1
  33. package/dist/components/agent-tools/prompt-studio/prompt-studio.class.js +1 -1
  34. package/dist/components/agent-tools/result-card/result-field.class.js +1 -1
  35. package/dist/components/agent-tools/schema-viewer/schema-viewer.class.js +1 -1
  36. package/dist/components/agent-tools/span-waterfall/span-waterfall.class.js +1 -1
  37. package/dist/components/agent-tools/stack-trace/stack-trace.class.js +1 -1
  38. package/dist/components/agent-tools/subagent-panel/subagent-panel.class.js +2 -2
  39. package/dist/components/agent-tools/task-list/task-list.class.js +2 -2
  40. package/dist/components/agent-tools/terminal/terminal.class.js +1 -1
  41. package/dist/components/agent-tools/test-results/test-results.class.js +1 -1
  42. package/dist/components/agent-tools/thinking-panel/thinking-panel.class.js +1 -1
  43. package/dist/components/agent-tools/tool-approval-dialog/tool-approval-dialog.class.js +1 -1
  44. package/dist/components/agent-tools/tool-call-block/tool-call-block.class.js +1 -1
  45. package/dist/components/agent-tools/tool-call-chip/tool-call-chip.class.js +1 -1
  46. package/dist/components/agent-tools/tool-param-form/tool-param-form.class.js +1 -1
  47. package/dist/components/agent-tools/tool-result-dialog/tool-result-dialog.class.js +1 -1
  48. package/dist/components/agent-tools/tool-result-view/tool-result-view.class.js +1 -1
  49. package/dist/components/agent-tools/tool-select-dialog/tool-select-dialog.class.js +1 -1
  50. package/dist/components/agent-tools/tool-timeline/tool-timeline.class.js +1 -1
  51. package/dist/components/agent-tools/trace-tree/trace-tree.class.js +2 -2
  52. package/dist/components/charts/chart/box-plot.class.d.ts +4 -2
  53. package/dist/components/charts/chart/box-plot.class.js +6 -5
  54. package/dist/components/charts/chart/chart-token-cache.js +2 -2
  55. package/dist/components/charts/chart/chart.class.d.ts +6 -4
  56. package/dist/components/charts/chart/chart.class.js +8 -7
  57. package/dist/components/charts/chart/histogram.class.js +1 -1
  58. package/dist/components/charts/chart/lite-chart.class.js +1 -1
  59. package/dist/components/conversation/agent-workspace/agent-workspace.class.js +1 -1
  60. package/dist/components/conversation/audio-visualizer/audio-visualizer.class.js +1 -1
  61. package/dist/components/conversation/branch-picker/branch-picker.class.js +1 -1
  62. package/dist/components/conversation/chat-composer/chat-composer.class.js +1 -1
  63. package/dist/components/conversation/chat-message/chat-message.class.js +1 -1
  64. package/dist/components/conversation/chat-viewport/chat-viewport.class.js +1 -1
  65. package/dist/components/conversation/checkpoint/checkpoint.class.js +1 -1
  66. package/dist/components/conversation/code-block/code-block-base.class.js +1 -1
  67. package/dist/components/conversation/code-block/code-block-core.class.js +1 -1
  68. package/dist/components/conversation/code-block/code-block.class.js +1 -1
  69. package/dist/components/conversation/conversation-item/conversation-item.class.js +1 -1
  70. package/dist/components/conversation/generation-metrics/generation-metrics.class.js +1 -1
  71. package/dist/components/conversation/handoff-divider/handoff-divider.class.js +1 -1
  72. package/dist/components/conversation/markdown/markdown-base.class.js +1 -1
  73. package/dist/components/conversation/markdown/markdown-core.class.js +1 -1
  74. package/dist/components/conversation/markdown/markdown.class.js +1 -1
  75. package/dist/components/conversation/message-actions/message-actions.class.js +1 -1
  76. package/dist/components/conversation/message-feedback/message-feedback.class.js +1 -1
  77. package/dist/components/conversation/message-parts/message-parts.class.js +1 -1
  78. package/dist/components/conversation/model-select/model-select.class.js +1 -1
  79. package/dist/components/conversation/model-settings-panel/model-settings-panel.class.js +1 -1
  80. package/dist/components/conversation/prompt-input/prompt-input.class.js +1 -1
  81. package/dist/components/conversation/prompt-queue/prompt-queue.class.js +1 -1
  82. package/dist/components/conversation/push-to-talk/push-to-talk.class.js +1 -1
  83. package/dist/components/conversation/realtime-session/realtime-session.class.js +1 -1
  84. package/dist/components/conversation/selection-toolbar/selection-toolbar.class.js +1 -1
  85. package/dist/components/conversation/stream-status/stream-status.class.js +1 -1
  86. package/dist/components/conversation/suggestion-chips/suggestion-chips.class.js +1 -1
  87. package/dist/components/conversation/thread-list/thread-list.class.js +1 -1
  88. package/dist/components/conversation/transcript-feed/transcript-feed.class.js +1 -1
  89. package/dist/components/conversation/typing-indicator/typing-indicator.class.js +1 -1
  90. package/dist/components/conversation/usage-badge/usage-badge.class.js +1 -1
  91. package/dist/components/conversation/voice-picker/voice-picker.class.js +1 -1
  92. package/dist/components/data/calendar/calendar.class.js +1 -1
  93. package/dist/components/data/condition-builder/condition-builder.class.js +1 -1
  94. package/dist/components/data/context-meter/context-meter.class.js +1 -1
  95. package/dist/components/data/data-grid/data-grid.class.d.ts +2 -2
  96. package/dist/components/data/data-grid/data-grid.class.js +3 -3
  97. package/dist/components/data/document-library/document-library.class.js +1 -1
  98. package/dist/components/data/env-list/env-list.class.js +1 -1
  99. package/dist/components/data/file-tree/file-tree.class.d.ts +2 -2
  100. package/dist/components/data/file-tree/file-tree.class.js +1 -1
  101. package/dist/components/data/flow-canvas/flow-canvas.class.js +1 -1
  102. package/dist/components/data/flow-controls/flow-controls.class.js +1 -1
  103. package/dist/components/data/flow-minimap/flow-minimap.class.js +1 -1
  104. package/dist/components/data/flow-node/flow-node.class.js +1 -1
  105. package/dist/components/data/flow-run-status/flow-run-status.class.js +1 -1
  106. package/dist/components/data/funnel/funnel.class.js +1 -1
  107. package/dist/components/data/gauge/gauge.class.js +1 -1
  108. package/dist/components/data/graph-query-builder/graph-query-builder.class.js +1 -1
  109. package/dist/components/data/heatmap/heatmap.class.d.ts +2 -1
  110. package/dist/components/data/heatmap/heatmap.class.js +2 -2
  111. package/dist/components/data/heatmap/heatmap.styles.js +1 -1
  112. package/dist/components/data/pagination/pagination.class.d.ts +2 -0
  113. package/dist/components/data/pagination/pagination.class.js +1 -1
  114. package/dist/components/data/pagination/pagination.styles.js +1 -1
  115. package/dist/components/data/sequence-strip/sequence-strip.class.js +1 -1
  116. package/dist/components/data/sparkline/sparkline.class.js +1 -1
  117. package/dist/components/data/stat/stat.class.js +1 -1
  118. package/dist/components/data/table/table.class.d.ts +23 -2
  119. package/dist/components/data/table/table.class.js +5 -3
  120. package/dist/components/data/table/table.styles.js +1 -1
  121. package/dist/components/data/timeline/timeline.class.js +1 -1
  122. package/dist/components/data/tree/tree.class.js +1 -1
  123. package/dist/components/data/word-cloud/word-cloud.class.js +1 -1
  124. package/dist/components/forms/button/button.class.js +1 -1
  125. package/dist/components/forms/checkbox/checkbox.class.js +1 -1
  126. package/dist/components/forms/checkbox-group/checkbox-group.class.js +1 -1
  127. package/dist/components/forms/code-editor/code-editor.class.js +1 -1
  128. package/dist/components/forms/color-picker/color-picker.class.d.ts +8 -4
  129. package/dist/components/forms/color-picker/color-picker.class.js +2 -2
  130. package/dist/components/forms/combobox/combobox.class.js +1 -1
  131. package/dist/components/forms/country-picker/country-picker.class.js +1 -1
  132. package/dist/components/forms/currency-picker/currency-picker.class.js +1 -1
  133. package/dist/components/forms/date-picker/date-input.class.js +1 -1
  134. package/dist/components/forms/date-picker/date-picker.class.js +1 -1
  135. package/dist/components/forms/emoji-picker/emoji-data-loader.js +1 -1
  136. package/dist/components/forms/emoji-picker/emoji-picker.class.d.ts +7 -4
  137. package/dist/components/forms/emoji-picker/emoji-picker.class.js +5 -5
  138. package/dist/components/forms/icon-button/icon-button.class.d.ts +4 -0
  139. package/dist/components/forms/icon-button/icon-button.class.js +1 -1
  140. package/dist/components/forms/icon-button/icon-button.styles.js +1 -1
  141. package/dist/components/forms/input/input-shared.js +1 -1
  142. package/dist/components/forms/input/input.class.js +1 -1
  143. package/dist/components/forms/input/native-time-input.class.js +1 -1
  144. package/dist/components/forms/input/number-input.class.js +1 -1
  145. package/dist/components/forms/input/time-input.class.js +1 -1
  146. package/dist/components/forms/locale-picker/locale-picker.class.js +1 -1
  147. package/dist/components/forms/otp-input/otp-input.class.js +1 -1
  148. package/dist/components/forms/phone-input/phone-input.class.js +1 -1
  149. package/dist/components/forms/radio/radio-group.class.js +1 -1
  150. package/dist/components/forms/radio/radio.class.js +1 -1
  151. package/dist/components/forms/rubric-form/rubric-form.class.js +4 -4
  152. package/dist/components/forms/select/select.class.js +1 -1
  153. package/dist/components/forms/signature-pad/signature-pad.class.js +1 -1
  154. package/dist/components/forms/slider/slider.class.js +1 -1
  155. package/dist/components/forms/switch/switch.class.js +1 -1
  156. package/dist/components/forms/textarea/textarea.class.js +1 -1
  157. package/dist/components/forms/time-range/time-range.class.js +1 -1
  158. package/dist/components/forms/time-zone-picker/time-zone-picker.class.js +1 -1
  159. package/dist/components/forms/token-input/token-input.class.js +1 -1
  160. package/dist/components/forms/unit-picker/unit-picker.class.js +1 -1
  161. package/dist/components/layout/app-rail/app-rail-item.class.js +1 -1
  162. package/dist/components/layout/app-rail/app-rail.class.d.ts +70 -7
  163. package/dist/components/layout/app-rail/app-rail.class.js +16 -4
  164. package/dist/components/layout/app-rail/app-rail.styles.js +1 -1
  165. package/dist/components/layout/app-rail-group/app-rail-group.class.js +1 -1
  166. package/dist/components/layout/breadcrumb/breadcrumb.class.js +1 -1
  167. package/dist/components/layout/card/card.class.js +1 -1
  168. package/dist/components/layout/carousel/carousel.class.js +1 -1
  169. package/dist/components/layout/command-palette/command-palette.class.js +1 -1
  170. package/dist/components/layout/dashboard-grid/dashboard-grid.class.js +1 -1
  171. package/dist/components/layout/details/accordion-item.class.js +1 -1
  172. package/dist/components/layout/details/details.class.js +1 -1
  173. package/dist/components/layout/dock-panel/dock-panel.class.js +1 -1
  174. package/dist/components/layout/drilldown-panel/drilldown-panel.class.js +1 -1
  175. package/dist/components/layout/filter-bar/filter-bar.class.js +1 -1
  176. package/dist/components/layout/menu/menu.class.js +1 -1
  177. package/dist/components/layout/menubar/menubar.class.js +1 -1
  178. package/dist/components/layout/multi-split/multi-split.class.js +1 -1
  179. package/dist/components/layout/navigation-menu/navigation-menu.class.js +1 -1
  180. package/dist/components/layout/page/page.class.js +1 -1
  181. package/dist/components/layout/reorder-list/reorder-item.class.js +1 -1
  182. package/dist/components/layout/reorder-list/reorder-list.class.js +1 -1
  183. package/dist/components/layout/responsive-panel/responsive-panel.class.js +1 -1
  184. package/dist/components/layout/scroller/scroller.class.js +1 -1
  185. package/dist/components/layout/segmented/segmented.class.d.ts +7 -4
  186. package/dist/components/layout/segmented/segmented.styles.js +1 -1
  187. package/dist/components/layout/split-panel/split-panel.class.js +1 -1
  188. package/dist/components/layout/tab-group/tab-group.class.js +1 -1
  189. package/dist/components/layout/tab-group/tab.class.js +1 -1
  190. package/dist/components/layout/virtual-list/virtual-list.styles.js +1 -1
  191. package/dist/components/layout/widget/widget.class.js +1 -1
  192. package/dist/components/media/animated-image/animated-image.class.js +1 -1
  193. package/dist/components/media/attachment-chip/attachment-chip.class.js +1 -1
  194. package/dist/components/media/attachment-trigger/attachment-trigger.class.js +2 -2
  195. package/dist/components/media/av-player/av-player.class.js +1 -1
  196. package/dist/components/media/avatar-group/avatar-group.class.js +1 -1
  197. package/dist/components/media/drop-zone/drop-zone.class.js +1 -1
  198. package/dist/components/media/file-icon/file-icon.class.js +1 -1
  199. package/dist/components/media/file-input/file-input.class.js +1 -1
  200. package/dist/components/media/flag/flag.class.js +1 -1
  201. package/dist/components/media/image-comparer/image-comparer.class.js +1 -1
  202. package/dist/components/media/image-viewer/image-viewer.class.d.ts +2 -1
  203. package/dist/components/media/image-viewer/image-viewer.class.js +4 -2
  204. package/dist/components/media/image-viewer/image-viewer.styles.js +1 -1
  205. package/dist/components/media/lightbox/lightbox.class.js +1 -1
  206. package/dist/components/media/map/map.class.js +1 -1
  207. package/dist/components/media/media-card/media-card.class.js +1 -1
  208. package/dist/components/media/pan-zoom/pan-zoom.class.js +1 -1
  209. package/dist/components/media/qr-code/qr-code.class.d.ts +2 -2
  210. package/dist/components/media/qr-code/qr-code.class.js +1 -1
  211. package/dist/components/media/sequence-playback/sequence-playback.class.js +1 -1
  212. package/dist/components/media/video/video.class.js +1 -1
  213. package/dist/components/media/video-playlist/video-playlist.class.js +1 -1
  214. package/dist/components/media/zoomable-frame/zoomable-frame.class.js +1 -1
  215. package/dist/components/overlays/alert/alert.class.js +1 -1
  216. package/dist/components/overlays/badge/tag.class.js +1 -1
  217. package/dist/components/overlays/callout/callout.class.js +1 -1
  218. package/dist/components/overlays/chip/chip-group.class.js +1 -1
  219. package/dist/components/overlays/chip/chip.class.js +1 -1
  220. package/dist/components/overlays/context-menu/context-menu.class.js +1 -1
  221. package/dist/components/overlays/dialog/confirm.js +1 -1
  222. package/dist/components/overlays/dialog/dialog.class.js +1 -1
  223. package/dist/components/overlays/kbd/kbd.class.js +1 -1
  224. package/dist/components/overlays/overlay/popover.class.js +1 -1
  225. package/dist/components/overlays/overlay/tooltip.class.js +1 -1
  226. package/dist/components/overlays/progress/progress-bar.class.js +1 -1
  227. package/dist/components/overlays/progress/progress-ring.class.js +1 -1
  228. package/dist/components/overlays/rating/rating.class.js +1 -1
  229. package/dist/components/overlays/skeleton/skeleton.class.js +1 -1
  230. package/dist/components/overlays/spinner/spinner.class.js +1 -1
  231. package/dist/components/overlays/toast/toast-item.class.js +1 -1
  232. package/dist/components/overlays/toast/toast.class.js +1 -1
  233. package/dist/components/retrieval/chunk-inspector/chunk-inspector.class.js +1 -1
  234. package/dist/components/retrieval/citation-badge/citation-badge.class.js +1 -1
  235. package/dist/components/retrieval/claim-evidence/claim-evidence.class.js +1 -1
  236. package/dist/components/retrieval/community-card/community-card.class.js +1 -1
  237. package/dist/components/retrieval/embedding-explorer/embedding-explorer.class.js +1 -1
  238. package/dist/components/retrieval/entity-card/entity-card.class.js +1 -1
  239. package/dist/components/retrieval/entity-chip/entity-chip.class.js +1 -1
  240. package/dist/components/retrieval/entity-dossier/entity-dossier.class.js +1 -1
  241. package/dist/components/retrieval/graph/graph.class.js +1 -1
  242. package/dist/components/retrieval/graph-legend/graph-legend.class.js +1 -1
  243. package/dist/components/retrieval/grounding-summary/grounding-summary.class.js +2 -2
  244. package/dist/components/retrieval/ingestion-queue/ingestion-queue.class.js +1 -1
  245. package/dist/components/retrieval/knowledge-base/knowledge-base.class.js +1 -1
  246. package/dist/components/retrieval/knowledge-base-admin/knowledge-base-admin.class.js +1 -1
  247. package/dist/components/retrieval/knowledge-graph-explorer/knowledge-graph-explorer.class.js +1 -1
  248. package/dist/components/retrieval/memory-panel/memory-panel.class.js +1 -1
  249. package/dist/components/retrieval/mind-map/mind-map.class.js +1 -1
  250. package/dist/components/retrieval/neighbor-list/neighbor-list.class.js +1 -1
  251. package/dist/components/retrieval/node-palette/node-palette.class.js +1 -1
  252. package/dist/components/retrieval/path-strip/path-strip.class.js +1 -1
  253. package/dist/components/retrieval/provenance-panel/provenance-panel.class.js +1 -1
  254. package/dist/components/retrieval/rag-answer/rag-answer.class.js +1 -1
  255. package/dist/components/retrieval/rag-eval-dashboard/rag-eval-dashboard.class.js +1 -1
  256. package/dist/components/retrieval/research-progress/research-progress.class.js +1 -1
  257. package/dist/components/retrieval/retrieval-compare/retrieval-compare.class.js +1 -1
  258. package/dist/components/retrieval/retrieval-results/retrieval-results.class.js +1 -1
  259. package/dist/components/retrieval/retrieval-search/retrieval-search.class.js +1 -1
  260. package/dist/components/retrieval/retrieval-trace/retrieval-trace.class.js +1 -1
  261. package/dist/components/retrieval/source-card/source-card.class.js +1 -1
  262. package/dist/components/retrieval/source-list/source-list.class.js +1 -1
  263. package/dist/components/retrieval/source-picker/source-picker.class.js +1 -1
  264. package/dist/components/utility/copy-button/copy-button.class.js +1 -1
  265. package/dist/components/utility/diff-view/diff-view.class.js +1 -1
  266. package/dist/components/utility/export-button/export-button.class.js +1 -1
  267. package/dist/components/utility/icon/icon.class.js +1 -1
  268. package/dist/components/utility/json-viewer/json-viewer.class.js +1 -1
  269. package/dist/components/utility/known-date/known-date.class.js +1 -1
  270. package/dist/components/utility/mention-popover/mention-popover.class.js +1 -1
  271. package/dist/components/utility/poll-status/poll-status.class.js +1 -1
  272. package/dist/components/utility/random-content/random-content.class.js +1 -1
  273. package/dist/components/utility/tour/tour.class.js +1 -1
  274. package/dist/components/viewers/archive-viewer/archive-viewer.class.js +1 -1
  275. package/dist/components/viewers/calendar-viewer/calendar-viewer.class.js +1 -1
  276. package/dist/components/viewers/contact-viewer/contact-viewer.class.js +1 -1
  277. package/dist/components/viewers/csv-viewer/csv-viewer.class.js +1 -1
  278. package/dist/components/viewers/dataset-viewer/dataset-viewer.class.js +2 -2
  279. package/dist/components/viewers/document-compare/document-compare.class.js +1 -1
  280. package/dist/components/viewers/document-preview/document-preview.class.js +1 -1
  281. package/dist/components/viewers/document-viewer/document-viewer.class.js +1 -1
  282. package/dist/components/viewers/docx-viewer/docx-viewer.class.js +1 -1
  283. package/dist/components/viewers/ebook-viewer/ebook-viewer.class.js +1 -1
  284. package/dist/components/viewers/email-viewer/email-viewer.class.js +1 -1
  285. package/dist/components/viewers/geojson-view/geojson-view.class.js +1 -1
  286. package/dist/components/viewers/geojson-view/geojson-viewer.class.js +1 -1
  287. package/dist/components/viewers/highlight-layer/highlight-layer.class.js +1 -1
  288. package/dist/components/viewers/html-viewer/html-viewer.class.js +1 -1
  289. package/dist/components/viewers/include/include.class.js +1 -1
  290. package/dist/components/viewers/notebook-viewer/notebook-viewer.class.js +1 -1
  291. package/dist/components/viewers/page-rail/page-rail.class.d.ts +1 -1
  292. package/dist/components/viewers/page-rail/page-rail.class.js +3 -3
  293. package/dist/components/viewers/pdf-viewer/pdf-viewer.class.d.ts +2 -2
  294. package/dist/components/viewers/pdf-viewer/pdf-viewer.class.js +2 -2
  295. package/dist/components/viewers/pptx-viewer/pptx-viewer.class.js +1 -1
  296. package/dist/components/viewers/spreadsheet-viewer/spreadsheet-viewer.class.js +1 -1
  297. package/dist/components/viewers/svg-viewer/svg-viewer.class.js +1 -1
  298. package/dist/components/viewers/xml-viewer/xml-viewer.class.js +1 -1
  299. package/dist/events.d.ts +6 -1
  300. package/dist/framework-types.d.ts +1 -1
  301. package/dist/internal/accessibility-visibility.js +1 -1
  302. package/dist/internal/collection-snapshot.js +1 -1
  303. package/dist/internal/default-strings/agent-tools.generated.js +1 -0
  304. package/dist/internal/default-strings/charts.generated.js +1 -0
  305. package/dist/internal/default-strings/conversation.generated.js +1 -0
  306. package/dist/internal/default-strings/data.generated.js +1 -0
  307. package/dist/internal/default-strings/forms.generated.js +1 -0
  308. package/dist/internal/default-strings/layout.generated.js +1 -0
  309. package/dist/internal/default-strings/media.generated.js +1 -0
  310. package/dist/internal/default-strings/overlays.generated.js +1 -0
  311. package/dist/internal/default-strings/retrieval.generated.js +1 -0
  312. package/dist/internal/default-strings/shared.generated.js +1 -0
  313. package/dist/internal/default-strings/utility.generated.js +1 -0
  314. package/dist/internal/default-strings/viewers.generated.js +5 -0
  315. package/dist/internal/dev-mode-attribute-warning.development.js +1 -1
  316. package/dist/internal/dev-mode-attribute-warning.production.js +1 -1
  317. package/dist/internal/document-tokens.d.ts +11 -0
  318. package/dist/internal/document-tokens.generated.js +15 -0
  319. package/dist/internal/document-tokens.js +3 -0
  320. package/dist/internal/form-associated.d.ts +1 -1
  321. package/dist/internal/form-associated.js +1 -1
  322. package/dist/internal/glass-surface.styles.js +1 -1
  323. package/dist/internal/host-tokens.styles.js +1 -0
  324. package/dist/internal/image-zoom.js +1 -0
  325. package/dist/internal/intl-cache.js +1 -1
  326. package/dist/internal/localization-types.d.ts +1 -1
  327. package/dist/internal/localization.js +2 -2
  328. package/dist/internal/lyra-element.d.ts +4 -2
  329. package/dist/internal/lyra-element.js +2 -2
  330. package/dist/internal/nonmodal-overlay-manager.js +1 -1
  331. package/dist/internal/opaque-content-border.styles.js +1 -1
  332. package/dist/internal/open-overlay-ancestors.js +1 -0
  333. package/dist/internal/open-overlay-raise.styles.js +1 -0
  334. package/dist/internal/optional-peer-failure.js +1 -0
  335. package/dist/internal/package-metadata.js +1 -1
  336. package/dist/internal/prefix.js +1 -1
  337. package/dist/internal/specialist-host-tokens.styles.js +1 -0
  338. package/dist/internal/specialist-tokens.generated.js +2 -0
  339. package/dist/internal/step-value.js +1 -1
  340. package/dist/internal/theme-observation.js +1 -1
  341. package/dist/internal/theme-scopes.d.ts +11 -0
  342. package/dist/internal/theme-scopes.js +1 -0
  343. package/dist/looks/shadcn.css +1 -1
  344. package/dist/lyra.d.ts +1 -1
  345. package/dist/lyra.js +1 -1
  346. package/dist/styles/design-tokens.css +1 -1
  347. package/dist/styles/tokens-root.css +1 -1
  348. package/dist/surfaces/glass.css +1 -1
  349. package/dist/testing/happy-dom-shims.d.ts +22 -1
  350. package/dist/testing/happy-dom-shims.js +1 -1
  351. package/dist/testing/index.d.ts +1 -1
  352. package/dist/testing/index.js +1 -1
  353. package/dist/testing/interaction-drivers.d.ts +66 -7
  354. package/dist/testing/interaction-drivers.js +1 -1
  355. package/dist/testing/jsdom-shims.d.ts +70 -0
  356. package/dist/testing/jsdom-shims.js +1 -0
  357. package/dist/theme/look-inputs.d.ts +1 -1
  358. package/dist/theme/look-inputs.js +37 -41
  359. package/dist/theme/theme.d.ts +13 -0
  360. package/dist/theme/theme.js +1 -1
  361. package/dist/theme.css +1 -1
  362. package/dist/utilities/index.d.ts +1 -1
  363. package/dist/utilities/index.js +1 -1
  364. package/dist/utilities/scoped-registry.d.ts +3 -0
  365. package/dist/utilities/scoped-registry.js +1 -1
  366. package/dist/utilities/theme-scopes.d.ts +1 -0
  367. package/dist/utilities/theme-scopes.js +1 -0
  368. package/dist/utilities/tokens.d.ts +1 -0
  369. package/dist/utilities/tokens.js +1 -0
  370. package/llms/components/lr-app-rail.md +51 -4
  371. package/llms/components/lr-box-plot.md +5 -0
  372. package/llms/components/lr-chart.md +6 -0
  373. package/llms/components/lr-emoji-picker.md +13 -8
  374. package/llms/components/lr-heatmap.md +10 -2
  375. package/llms/components/lr-icon-button.md +13 -1
  376. package/llms/components/lr-image-viewer.md +8 -0
  377. package/llms/components/lr-pagination.md +4 -2
  378. package/llms/components/lr-segmented.md +7 -3
  379. package/llms/components/lr-table.md +14 -12
  380. package/llms/components/lr-thread-list.md +4 -3
  381. package/llms/migration.md +9 -0
  382. package/llms/peers.md +1 -1
  383. package/llms/shared/events-and-types.md +41 -0
  384. package/llms/shared/frameworks-and-ssr.md +16 -0
  385. package/llms/shared/imports-and-registration.md +4 -3
  386. package/llms/shared/native-styles-and-utilities.md +38 -3
  387. package/llms/shared/styles-and-tokens.md +242 -102
  388. package/llms/shared/testing-and-utilities.md +104 -7
  389. package/llms/shared.md +445 -115
  390. package/llms/tokens.md +31 -49
  391. package/locales.json +138 -138
  392. package/package.json +16 -8
  393. package/registrations.json +1 -1
  394. package/dist/internal/default-strings.generated.js +0 -5
  395. package/dist/internal/specialist-tokens.styles.js +0 -1
  396. package/dist/internal/tokens/palette.styles.js +0 -1
  397. package/dist/internal/tokens.styles.js +0 -1
package/CHANGELOG.md CHANGED
@@ -1,76 +1,80 @@
1
1
  # Changelog
2
2
 
3
- ## 27.0.0
3
+ ## 28.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - be521ef: Add independent heatmap hover-ring styling while preserving keyboard focus feedback. Contain deferred fitted heatmap frames after their allocation shrinks, preserve chart data when a canvas context cannot initialize, and keep anchored overlays above sticky cells when composed through shadow roots. The Happy DOM setup helper now supplies missing shadow-root stylesheet collections and HTML direction hints.
8
+
9
+ Review bundled-size allowances for the added overlay ancestry tracking, canvas fallback, and heatmap containment. Only exceeded JavaScript and component aggregate limits advance to their measured sizes; existing passing limits, optional-peer exclusions, and CSS budgets remain unchanged.
10
+
11
+ ### Patch Changes
12
+
13
+ - @aceshooting/lyra-translations@28.1.0
14
+
15
+ ## 28.0.0
4
16
 
5
17
  ### Major Changes
6
18
 
7
- - fe2bb4e: Agent-tools consolidation. Migration notes:
8
-
9
- - `ToolCallStatus` and `ToolResultStatus` are removed; import the single `ToolStatus` union instead (`'pending' | 'running' | 'success' | 'error' | 'denied' | 'incomplete'`), also exported from the root barrel. The tool-call chip, result dialog, result view, timeline and block now share one status keyframes fragment (`lr-tool-status-spin` / `lr-tool-status-pulse`); the public `--lr-*-spin` hooks are unchanged.
10
- - `lr-task-list` and `lr-prompt-studio` now default `heading-level` to `none`, matching `lr-result-card`; set `heading-level="3"` / `"2"` to keep the previous semantic headings.
11
- - `lr-prompt-studio` and `lr-context-inspector` honor `label=""` verbatim instead of falling back to the heading or localized default.
12
- - `lr-tool-approval-dialog` now carries `waitUntil(promise)` on `lr-approve-request` / `lr-deny-request` (`lr-deny-request` detail is `{ waitUntil }` instead of `null`) and emits `lr-decision-settled`, like `lr-confirm-bar`.
13
- - `lr-eval-dataset`'s search field is now a composed `<lr-input type="search" clearable>`: the `--lr-eval-dataset-search-*` custom properties are removed (use `--lr-input-*` / `--lr-form-control-*`), `search-input` is the `lr-input` host, and the native input and clear button are exposed as `search-input-field` and `search-clear`.
14
- - `lr-permission-grant` decision buttons and `lr-connector-manager` action buttons are now `<lr-button>`s; parts `decision` / `action` are the button hosts (new `decision-base`, `decision-label`, `action-base`, `action-label`).
15
- - The duplicate `evaluationRunStatusIdle`, `evaluationRunStatusWaitingInput`, `evaluationRunStatusWaitingApproval` and `evaluationRunStatusCancelled` locale keys are removed; `lr-evaluation-run` uses the `agentRunStatus*` keys.
16
- - `lr-terminal` snapshots its line list once per render instead of once per write.
17
- - fe2bb4e: Charts over the 1,000-record budget now keep every series plotted and in the legend and thin only the rows (first, last and evenly spaced). Previously interior series were dropped. Series are thinned only beyond 500. `lr-histogram` no longer bundles the sync-group styles statically.
18
- - fe2bb4e: `lr-phone-input` now matches `lr-input`/`lr-textarea` native passthrough: `autocorrect` is a boolean (the string `autoCorrect` property is removed; use `autocorrect`, `.autocorrect=${false}` or `autocorrect="off"`), `inputMode`/`enterKeyHint` are the camel-case properties with the lowercase names kept as aliases, and the host `name` is forwarded to the internal field (also on `lr-textarea`). `lr-input`, `lr-textarea` and `lr-phone-input` share one per-appearance fill/border fragment, and `lr-time-range` and `lr-date-picker` share one preset-button paint (hover now fills and recolors the border on both; new `--lr-time-range-preset-hover-bg` and `--lr-date-picker-preset-{hover,pressed,selected}-border-color` tokens, old names kept as fallbacks). A focusout while the host is already `:disabled` no longer marks a form-associated control as interacted. `lr-date-input` follows the same shape: the string `autoCorrect` property is removed in favour of the boolean `autocorrect`, the lowercase `inputmode`/`enterkeyhint` aliases exist, and host `name` is forwarded to its native input (as it is on `lr-otp-input`). `lr-date-input` and `lr-time-input` now take their per-appearance fill and border from the same shared fragment. `lr-time-range` relays host `focus`/`blur` once on entering and leaving the control, no longer when focus moves between its two handles. `lr-model-select`, `lr-voice-picker` and `lr-menu` share the popup controller's outside-press binding; `lr-model-select` and `lr-voice-picker` now share one form-association implementation. `lr-model-select` and `lr-voice-picker` likewise replace the string `autoCorrect` property with the boolean `autocorrect` (use `autocorrect`, `.autocorrect=${false}` or `autocorrect="off"`).
19
- - fe2bb4e: Conversation and utility follow-ups. Migration notes:
20
-
21
- - `lr-prompt-input` gains `frame`, `actions-layout` and a forwarded `toolbar` slot, passed to its composed `lr-chat-composer`.
22
- - The `lr-chat-message` collapse button now defaults its hover fill and color to the shared icon-button surface (`--lr-color-surface-raised` / `--lr-color-text`) instead of the brand tint; set `--lr-icon-button-bg-hover` / `--lr-icon-button-color-hover` to keep the previous look.
23
- - `lr-flag` and `lr-qr-code` mount their announcement regions when a load or encode starts rather than on connect.
24
- - fe2bb4e: The editor data and the built-in locale catalogs moved out of `@aceshooting/lyra-ui` into two companion packages, cutting the install from about 35 MB to about 20 MB unpacked.
25
-
26
- - Locales: install `@aceshooting/lyra-translations` and change `@aceshooting/lyra-ui/translations/<locale>.js` (and `<locale>/<family>.js`) to `@aceshooting/lyra-translations/<locale>.js`. The package is an optional peer of `@aceshooting/lyra-ui`; `loadLyraLocale()` from `@aceshooting/lyra-ui/locale-loader.js` now imports its catalogs from it, so install it wherever that loader is used. The pseudo-locales (`@aceshooting/lyra-ui/translations/pseudo/en-XA.js` and `ar-XB.js`) stay in `@aceshooting/lyra-ui`. `locales.json` now lists the new specifiers.
27
- - Editor data: install `@aceshooting/lyra-ide` for `custom-elements.json`, `web-types.json`, `vscode-html-data.json` and `vscode-css-data.json`. Change `@aceshooting/lyra-ui/custom-elements.json` to `@aceshooting/lyra-ide/custom-elements.json` and the VS Code `html.customData` / `css.customData` paths to `./node_modules/@aceshooting/lyra-ide/...`. `@aceshooting/lyra-ui` no longer has the `customElements` and `web-types` package fields or the `./custom-elements.json` export.
28
-
29
- The three packages are versioned together.
30
- - fe2bb4e: Media follow-ups. Migration notes:
31
-
32
- - `lr-file-input`'s `multiple` now reads `multiple="false"` as false, like `lr-drop-zone` and `lr-attachment-trigger`; remove the attribute instead of setting it to `"false"` only if you relied on it enabling batches.
33
- - `lr-drop-zone` gains `allowedMimeTypes`, `forbiddenMimeTypes`, `accepted-message` and `rejected-message`, matching `lr-file-input`.
34
- - `lr-video`'s speed select is named by `avPlayerPlaybackRate`, shared with `lr-av-player`; a `videoPlaybackSpeed` string override no longer applies, so move it to `avPlayerPlaybackRate`.
35
- - `lr-lightbox` no longer navigates on Arrow keys pressed in a slotted `actions` button or link.
36
- - `lr-zoomable-frame` shares one set of owner-document focus listeners across frames and drops its theme watcher when `with-theme-sync` is turned off.
37
- - fe2bb4e: Every copy control now shows its copied/failed feedback for the same 1500 ms (`lr-copy-button`, `lr-diff-view`, `lr-json-viewer`, `lr-stack-trace`, code blocks and the markdown code header). Breaking: `lr-copy-button`'s `feedbackDuration` default changes from `1000` to `1500`; set `feedback-duration="1000"` to keep the old timing.
19
+ - bf50c85: Declare the shared design-token layer once per document (RFC 0002). The resolved `--lr-*` outputs are no longer re-declared on every component host: one document token layer declares them on `:root` and re-derives them only at theme scopes (`:root`, `.lr-light`/`.lr-dark`, `[data-lr-theme]`, the style-axis boundaries, the design-token fixture scopes, a look's scoped `.light`/`.dark` aliases, and the new `data-lr-theme-scope` marker). Mode reaches every scope through two inherited switches, so every engine now follows the nearest mode scope, with or without `theme.css`. Components keep only the host-local names (`--lr-icon-button-size`, `--lr-radius-button`, the logical safe-area aliases) and the forced-colours, increased-contrast and reduced-motion arms, so an application override still cannot defeat a preference inside a component. Server-rendered declarative shadow roots shrink by about 34 KB per element.
20
+
21
+ New: `adoptLyraTokens(root)` (`utilities/tokens.js`, also from the package root), `findUnscopedThemeInputs(root?)` (`utilities/theme-scopes.js`), the `data-lr-theme-scope` attribute, and `lyra-ui-migrate --rule=theme-scopes`. `theme.css` now ends with that layer, so a page that imports `theme.css` has it from first paint and the runtime adopts no copy; on a page without `theme.css`, registering the first Lyra element adopts the same text as a constructed stylesheet. `tokens-root.css` is now the whole layer alone (byte-identical), for pages that do not use `theme.css` (link one of the two, not both); its stable public subset is listed in its header and gained `--lr-transition-interactive`, which the guide already promised. `theme.css` and the look mode aliases no longer declare the focus-ring composite; the layer owns those four names with the same values. Carrying the layer makes `theme.css` (and every composition that includes it) about 3 KB gzip larger. The runtime recognises a static copy anywhere in a stylesheet (a bundler that inlines `@import '…/theme.css'` mid-file), in a `<link>` or `<style>` whose media matches, and from a cross-origin `theme.css` it cannot read, so the page never applies two copies.
22
+
23
+ Migration:
24
+
25
+ - **A `--lr-theme-*` input set on an element that is not a theme scope** (a wrapper, a component's inline style, or an application component's own `:host` rule) no longer re-derives the components below it. Add `data-lr-theme-scope` to that element (use `data-lr-theme-scope=""` in JSX, where `false` is rendered as the string `"false"`, which still marks). Run `npx lyra-ui-migrate --rule=theme-scopes src` to mark inline styles in HTML, Lit and JSX templates and to list dynamic `setProperty()` inputs and stylesheet rules that need the marker. Inputs read on the host itself (chart, graph and terminal palettes, form-control heights and radius, icon-button size, button radius) keep working anywhere; `llms/tokens.md` lists them.
26
+ - **A `--lr-*` output set on an element** still applies there, but the outputs derived from it (`--lr-focus-ring` from `--lr-focus-ring-color`, `--lr-transition-interactive` from `--lr-transition-fast`, `--lr-color-brand` from `--lr-color-brand-fill-loud`) follow it only if the element is a theme scope; the migration rule marks inline ones. An output set on an ancestor now reaches every component below it until the next theme scope, instead of stopping at the first component.
27
+ - **Server-rendered pages** link `theme.css` (or, without it, `@aceshooting/lyra-ui/tokens-root.css`) for a correct first paint before hydration, and `tokens-root.css` inside an application's own declarative shadow roots that contain theme scopes.
28
+ - **Application shadow roots**: the layer is appended on demand where a Lyra element connects at or below a scope in that root, following the flat tree (a Lyra element slotted into your component counts as below the scopes around its slot, also when the component renders its slots right after connecting, as Lit does). The copy adopted into an application root is unlayered with zero-specificity (`:where()`) selectors, which keeps nested application roots as fast as before; your component's unlayered rules in that root still win over it, a rule inside a cascade layer of your own no longer does. Elements defined through `createScopedRegistry()` count as library components, as with `defineElement()`: their own shadow roots never receive the layer on demand. Call `adoptLyraTokens(root)` for scopes added afterwards, or for a root with scopes and no Lyra element yet. Append to `adoptedStyleSheets`, never replace it; a wholesale replacement removes the layer until the next Lyra element connects there.
29
+ - **Application elements that read `--lr-*` outputs** get resolved values wherever the layer applies: from first paint with `theme.css` or `tokens-root.css`, otherwise from the moment the first Lyra element is registered. On a page with no Lyra stylesheet, the adopted sheet's layer statement comes last, so declare Lyra's layer order first or keep application overrides of `--lr-*` outputs unlayered.
30
+ - **Mode without `theme.css`** now follows the nearest mode scope in every engine: an ancestor `.lr-dark` darkens components in Firefox and WebKit too, a `.lr-light` island inside it stays light, and `.lr-light` wins over an OS dark preference. Switching the root mode therefore restyles the page in those engines too, at about the cost the switch already had with `theme.css`.
31
+ - **Two copies of Lyra** on one page share an identical layer; when their layers differ, the later-adopted one wins and the development build warns.
32
+ - **Roles that follow brand in the default look** (Shadcn's neutral loud tier) now compile to the same follow switch as the explicit `data-lr-look="shadcn"` look and re-read the brand input on every boundary. Before, `theme.css` resolved them once at `:root`, so a brand input written on `:root` recoloured them inside every nested scope while brand itself kept the scope's value. On `:root` they still follow a brand input written there. The `data-lr-theme-scope` marker is not a boundary, so a brand input on a marked region leaves them at the nearest boundary's brand: for a brand region use `applyLyraStyleScope(el, { accent })` (which writes `data-lr-accent="custom"`) or set the neutral loud inputs there too.
33
+ - **Opaque content interiors** (`lr-card`, `lr-details`, `lr-accordion-item` bodies) restore the unqualified `--lr-color-border` and `--lr-color-border-strong` of the nearest theme scope for their slotted content, so a `--lr-color-border` output set on the component itself no longer reaches that content. Set `--lr-theme-color-surface-border` on a theme scope, or use the component's own hooks (for example `--lr-input-border-color`).
34
+ - **`utilities.css` and `native.css` typography** read the document layer like every other application element, so with the default Shadcn look they now use its values instead of their Lyra-look fallbacks (for example a `.lr-typography` `h5` or an `lr-prose` `h4` at the body size, `--lr-font-size-m`, and inline-code chips at `--lr-radius-xs`).
35
+ - **Chromium only:** the runtime also appends a one-rule sheet to `document.adoptedStyleSheets` that declares a private root property following every theme input. Re-theming the root (an accent or spacing input on `<html>`) with dark islands or other scopes on the page then costs about what it did in 27.0.0 instead of up to twice as much. Firefox and Safari do not receive it. When `theme.css` or `tokens-root.css` already provides the layer, the sheet goes in at the first change on `<html>` (an inline input, a mode attribute or class) instead of at the first connect, so a route that registers Lyra late on a large page does not restyle every element for it.
36
+ - **Known costs, accepted:** a page with thousands of marked rows that each carry different inline inputs pays more on its first style flush than 27.0.0 did (about +24 % at 3,000 rows in Chromium; each such row re-derives the shared outputs), and in Firefox a root spacing write on a page whose application hosts are marked scopes costs up to about 15 % more, because marked hosts now re-derive where 27's markers stopped inputs. In Safari, a spacing or font input written on `<html>` costs about 7 % more than in 27.0.0 on a page with dark islands (the same write without islands is about 35 % cheaper), because each element below an island compares the island's re-declared colour values before reaching the changed one. Per-family scopes, which would narrow these, are planned after 28.
37
+ - **Migrating from 27:** run `npx lyra-ui-migrate --rule=theme-scopes src` (the `--origin=lyra-v26` profile runs the same rule for projects coming from 26).
38
38
 
39
39
  ### Minor Changes
40
40
 
41
- - fe2bb4e: Retrieval, graph and agent-tools follow-ups:
42
-
43
- - `lr-retrieval-results` keeps the row inspector's `lr-toggle` inside the list, as it already did for `lr-chunk-toggle`.
44
- - `lr-knowledge-graph-explorer` shows a localized `details-limit` notice when `entityDetails` is too large to keep.
45
- - Score `thresholds` on chunk-inspector, memory-panel and grounding-summary ignore non-finite values and treat an inverted `{ high, medium }` pair as its ordered equivalent.
46
- - `lr-research-progress` renders its empty state through `lr-empty` (part `empty` is now an `lr-empty`).
47
- - `lr-tool-timeline` loads `lr-tool-approval-dialog` on first use, when an entry sets `needsApproval`, instead of at import.
48
- - `lr-json-schema-viewer` passes the selected schema node to `lr-schema-select` by identity instead of re-cloning it.
49
- - Canvas graph drawing allocates no per-node `Path2D` or per-link dash copy.
50
- - fe2bb4e: `lr-command-palette` now emits `lr-hide` (non-cancelable, `{ reason }`, after an accepted `lr-close-request`), `lr-after-show` and `lr-after-hide`, matching the dialog lifecycle vocabulary. Veto a close through `lr-close-request`, as before. The `videoPlaybackSpeed` locale string was removed; `lr-video` reads `avPlayerPlaybackRate`.
51
- - fe2bb4e: `lr-memory-panel`, `lr-retrieval-compare`, `lr-retrieval-trace` and `lr-research-progress` gain `frame` (`'card'` | `'plain'`) and `size` (dense under `s` and smaller). A `lr-chunk-inspector` with `ordinalIndex` set (every `lr-retrieval-results` row) no longer renders its own group, list and listitem semantics. Optional-peer load failures for `notebook-viewer`/`lr-icon` sanitizing, `pdfjs-dist`, `epubjs`, `mammoth` and `qrcode` now emit the shared fixed dev diagnostic (no error object) and a failed load is retried on the next use instead of being cached.
52
- - fe2bb4e: Add `CheckedFormAssociated`, a checked-mode variant of the `FormAssociated` mixin (value submitted only while `checked`, native `defaultChecked`/dirty semantics, reset and state restore, required means checked), and move `<lr-checkbox>`, `<lr-switch>` and `<lr-radio>` (and `<lr-radio-button>`) onto it. The mixin carries the shared form surface (`form`, `labels`, `validity`, custom validity, fieldset disablement, reset, restore), with hooks for group disablement, the veto-guard and owner bookkeeping, and the control's own validity message; behavior is unchanged. Each control gains the native `defaultValue` alias of `value` and the mixin's `setFormValue()`/`internals`. The dark `--lr-color-surface-overlay` default is now authored as a fixed `#171717` in the canonical token source, matching the runtime.
53
- - ee0b543: `lyra-ui-migrate --origin=lyra-v26` moves a project from Lyra 26 to 27. It rewrites `@aceshooting/lyra-ui/translations/<locale>` specifiers (the pseudo-locales stay) to `@aceshooting/lyra-translations/<locale>` and `custom-elements.json`, `web-types.json`, `vscode-html-data.json` and `vscode-css-data.json` to `@aceshooting/lyra-ide/...`, re-binds root imports of `ToolCallStatus` and `ToolResultStatus` as `ToolStatus as <Old>`, and reports removed localization keys, deep imports of the removed types and computed specifiers (`GLOBAL_REVIEW`). Migration profiles gain the optional `globals` list described in RFC 0003.
54
- - fe2bb4e: `lr-select`, `lr-combobox`, `lr-date-input`, `lr-time-input` and `lr-locale-picker` now share one popup controller for the open/veto/settle sequence and capture-phase outside-press dismissal, so `lr-time-input` and `lr-date-input` settle and dismiss identically. `lr-voice-picker` now takes its `catalog` through the same shared collection boundary as `lr-model-select`: `el.catalog` is a frozen clone of the assigned array, while rendering still admits at most the first 1,024 valid rows.
55
- - fe2bb4e: The package now bundles the agent skills and ships a `lyra-ui` executable. AI coding agents (Claude Code, Codex, OpenCode, Cursor, Gemini CLI, GitHub Copilot, Amp, Windsurf) run `npx lyra-ui init-agents` (add `--agent <name>` if not auto-detected) to install the `lyra-ui` and `compose-lyra-interfaces` skills for themselves; it prints the installed paths and the step that makes the agent load them (`--json` for a machine-readable result). The canonical copy goes to the shared `.agents/skills/` as symlinks into `node_modules` (so upgrades refresh them); agents that need their own directory (Claude Code: `.claude/skills/`) get a link to it. The invoking agent is detected from its documented environment (Claude Code, Gemini CLI), then project config; humans at a terminal are asked which agents to set up, and CI or agents get the detected defaults without a prompt. `--agents claude,opencode|all`, `--copy`, `--link`, `--dry-run`, `--force`, `--yes` and `--dir` are available. A marked `<!-- lyra-ui:start -->` block is added or refreshed in `AGENTS.md` (created if missing) and in `CLAUDE.md` (only if it exists and does not import `AGENTS.md`). It is idempotent, never edits content outside its own block or skill directories, and runs no postinstall. The bundled skills reference the package's own `llms/` directory; `lyra-ui migrate-wa` forwards to `lyra-ui-migrate`, which is unchanged.
56
- - fe2bb4e: `lr-table` gains `--lr-table-surface` (one surface for frame, rows, sticky header and columns) and `--lr-table-radius`; the frame now clips painted content to its radius under `scroll-mode="auto"` while content fits.
57
- - fe2bb4e: `@aceshooting/lyra-ui/testing` gains `installHappyDomShims()`, `installHappyDomShadowFocusShim()` and `installHappyDomAriaControlsShim()`. Under Happy DOM, `ShadowRoot.activeElement` on a sibling shadow root now returns `null` instead of throwing, and `Element.ariaControlsElements` is reflected. Both are no-ops on engines that already behave. Consumers can drop their own patches and call `installHappyDomShims()` in their setup file.
58
- - fe2bb4e: `lr-alert`, `lr-callout`, `lr-chip`, `lr-badge` and `lr-tag` now accept one shared `variant` set (`neutral`, `brand`, `primary`, `success`, `warning`, `danger`); `primary` is an alias of `brand` everywhere and each component keeps its own fallback for unsupported values. `lr-chip` gains `with-remove` (alias of `removable`) and `lr-tag` gains `disabled`, which disables its remove button and suppresses `lr-remove`. `lr-progress-bar` now exposes `indicator` and `indicatorOffset` like `lr-progress-ring`. `lr-mutation-observer` delivers records queued before an option change instead of dropping them. `lr-widget` reads its `label` slot presence from the shared slot-presence controller.
41
+ - de6b148: Add an opt-in `auto-hide` ("peek") mode to `<lr-app-rail>`. The desktop rail rests as the icon-only strip, expands to the full rail as an overlay (the strip's footprint stays in the layout, so the content never reflows) while a moving non-touch pointer or keyboard focus is inside, and docks at full width only when the user pins it with the new `[part="pin-button"]`. Tune it with `peek-open-delay` and `peek-close-delay`; observe it through the read-only reflected `peeking` state and the new `lr-peek-change` event. Escape closes the peek. The pin writes `preferredMode`, so it persists through `storage-key` with `persist="preferred-mode"`. New `appRailPin`/`appRailUnpin` strings ship in every locale catalog. Without `auto-hide` the rail behaves exactly as before.
42
+ - 39b4dd0: Add the public `--lr-theme-surface-glass-dark-share` theme input (a percentage, default `10%`) for how much of the theme surface dark Glass chrome keeps in its deepening anchor. Apps that lift their dark surface tokens to a softer grey can raise it, on `:root` or on a container, to get lighter glass without private overrides. The default paints identically. The input is not registered with `@property` (a registered inherited property slows every re-theme in Chromium), so a value that is not a percentage voids the dark fill; the development build warns about one set on `:root`. Raising it trades text contrast over bright backdrops, as documented in the styles and tokens guide.
43
+ - 4eadd60: `<lr-emoji-picker>` now tells a missing `emoji-picker-element-data` peer apart from one that is installed but fails to load. A peer whose module cannot be resolved logs "is not installed" with the install command and shows the new localized `emojiPickerPeerMissing` message ("Emoji are not available.") without a Retry button. A peer that was found but could not be loaded or validated (a network or JSON/import-attribute error, an incompatible version, a module without the expected data array) logs that cause with the underlying error and keeps "Could not load emoji." with `load-retry`. Both cases still fail closed, announce the message once and emit `lr-load-error`. A missing peer is imported and warned about once per page, for every picker and locale, since it cannot appear later; a failed load is still retried. `emojiPickerPeerMissing` is translated in every shipped catalog; override it through `registerLyraLocale()` or `.strings`.
44
+ - 5a25138: `@aceshooting/lyra-ui/testing`: `installHappyDomShims()` now also installs `installHappyDomCascadeLayerShim()`, and both it and `flattenCascadeLayers(css)` are exported. Happy DOM 20 drops every rule inside an `@layer` block, so the probe properties Lyra's stylesheets declare in cascade layers never resolved: under a Vitest + Happy DOM suite `theme.css` looked absent, `setLyraStyle()` warned that a stylesheet was missing, and it took its no-stylesheet fallback. The shim unwraps layer blocks in the text `CSSStyleSheet.replaceSync()`/`replace()` parse (the path used by `<style>`, linked and constructed sheets), so the probes resolve as in a browser. Layer precedence is not modelled. The shim detects the defect, so it is a no-op in every browser, and it returns a function that restores the original methods. Install it in the same `setupFiles` entry, before any stylesheet is parsed.
45
+ - 458b832: `<lr-icon-button>` documents `--lr-icon-button-padding-inline` (default `0`), the logical inline padding of its native control. A trigger that shows a short text value such as `€ EUR` no longer needs a `::part(button)` rule for breathing room. The default keeps an icon-only button at exactly its `--lr-icon-button-size` hit-area floor, which still applies with padding set.
46
+ - bcd2daa: `@aceshooting/lyra-ui/testing` exports `installJsdomShims()`, `installJsdomAdoptedStyleSheetsShim()` and `installJsdomFormAssociatedShim()` for jsdom suites. jsdom has no `adoptedStyleSheets`, so Lit rendered one `<style>` per component shadow root; jsdom registers each of those sheets on the document, so every `getComputedStyle()` call matched against all of them (one consumer measured about 118 ms per call and 70–115 s per test with about 357 sheets). The shim adds an inert `adoptedStyleSheets` to `Document` and `ShadowRoot` so Lit adopts constructed stylesheets instead; adopted sheets are stored but never applied. It installs only under jsdom while `adoptedStyleSheets` is missing, is a no-op in browsers and Happy DOM, and returns a function that removes it. Call it in a `setupFiles` entry before anything imports Lit. `installJsdomFormAssociatedShim()` (also installed by `installJsdomShims()`) completes jsdom's `ElementInternals`, which lacks form association: without it every Lyra form control (`lr-button`, `lr-input`, `lr-checkbox`, …) throws in its constructor under jsdom. It adds inert `setFormValue()`, a `setValidity()` that `validity`, `validationMessage`, `checkValidity()` and `reportValidity()` reflect, `willValidate`, `form` and `states`, only where missing. A jsdom 30 smoke run of the built package now renders buttons, cards, badges, checkboxes and inputs with the shims.
47
+ - 0a55cfb: `<lr-pagination format="compact">` no longer renders the page input touching its "/ N" page count: `[part="page-field"]` now has a small default gap (`var(--lr-space-2xs)`), themeable through the new `--lr-pagination-page-field-gap`, alongside `--lr-pagination-controls-gap` and `--lr-pagination-pages-gap`.
48
+ - b6636b8: `<lr-segmented>`'s checked segment is distinguishable on its own in every look and mode. It was a surface-coloured pill with a hairline shadow, about 1:1 against a surface-coloured card or dialog (WCAG 1.4.11). The defaults are now `--lr-segmented-selected-bg: var(--lr-color-text)` and `--lr-segmented-selected-color: var(--lr-color-surface)`, an inverted pill at 13:1 or more against its container. Consumers that override `--lr-segmented-selected-bg` should also set `--lr-segmented-selected-color` to a label that contrasts with their fill; overrides keep winning.
49
+ - 0845756: `@aceshooting/lyra-ui/theme.js` exports `startLyraStyle()`, a storage-free runtime initializer for pages that inline only `lyraThemeBootstrap` and load the style runtime later. It adopts the saved style (or the defaults) on the document root and, while the mode is `system`, attaches the live `prefers-color-scheme` listener. It never calls `localStorage.setItem()`/`removeItem()`, does not migrate a v1 record, emits no `lr-style-change` of its own, and is idempotent. The previous workaround, `setLyraStyle({})`, persisted the record as a side effect.
50
+ - 0fea4be: `<lr-table>` forwards its built-in pager's options: `pagination-with-summary` (`paginationWithSummary`) shows the item-range summary at the inline start of the footer with the controls at the inline end, and `pagination-format` (`paginationFormat`, `'compact'` by default, or `'standard'`) selects the pager format. The nested pager's parts are exported with a `pagination-` prefix (`pagination-summary`, `pagination-controls`, `pagination-pages`, `pagination-page`, `pagination-page-current`, `pagination-page-field`, `pagination-page-input`, `pagination-page-count`, `pagination-button`), so they can be styled without reaching into `::part(pagination)`.
51
+ - c5ea949: `@aceshooting/lyra-ui/testing` adds four interaction drivers: `chooseSwatch(picker, value)` for `<lr-swatch-picker>`, `chooseCurrency(picker, code)` for `<lr-currency-picker>` (including a `searchable` picker's lazily loaded combobox), and `openPopover(popover, options?)`/`closePopover(popover, options?)` for `<lr-popover>`. They act through the components' own pointer and keyboard paths (a swatch or option click, a trigger click, Escape or `{ via: 'trigger' }`), resolve after the component's own events (`lr-change`/`lr-activate`, the picker's `input`/`lr-input`/`change`/`lr-change`, `lr-after-show`/`lr-after-hide`), and throw instead of silently doing nothing when the interaction is refused (disabled, unknown value, vetoed lifecycle, manual trigger). Like the existing drivers they use plain DOM operations, with no test-runner-only helper.
59
52
 
60
53
  ### Patch Changes
61
54
 
62
- - 902c90b: `lr-emoji-picker` loads its `emoji-picker-element-data` datasets with the `with { type: 'json' }` import attribute, so the default emoji set now loads under Node's native ESM loader (for example Vitest without inlining `@aceshooting/lyra-ui`) as well as in bundlers. Consumers can drop any `server.deps.inline` workaround for the picker. The build no longer strips the attribute from the published files, and both a source-policy rule and a build-artifact check reject JSON imports without it.
63
- - fe2bb4e: Arrow/Home/End navigation in `lr-radio-group`, `lr-toggle-group`, `lr-sequence-strip` and `lr-tree` now resolves through one shared list-move helper: Ctrl/Alt/Meta-modified arrows in the radio and toggle groups are left to the platform instead of moving selection. `lr-checkbox-group` coalesces the render-driven child syncs of one task into a single reconciliation.
64
- - fe2bb4e: Hand-wired form controls now inherit `getForm()`, `labels`, `validity`, `validationMessage` and `willValidate` from one shared base instead of per-class copies; `lr-slider` label, readout, reference and hint text now wrap at word boundaries (`overflow-wrap: break-word`). No API change.
65
- - fe2bb4e: `lr-csv-viewer`, `lr-dataset-viewer` and `lr-spreadsheet-viewer` share one table-viewer controller, and `lr-docx-viewer`/`lr-pdf-viewer` share one search-state helper; `lr-archive-viewer`, `lr-html-viewer` and `lr-svg-viewer` adopt the shared viewer frame styles. No public API or rendered output changes.
66
- - fe2bb4e: Internal cleanup: the unused `restoreFromStorage()` helper is removed from the internal persisted-restore module (it was never part of the public API); popover and tooltip share one anchor-identity tracker and trigger resolver.
67
- - fe2bb4e: Overlay anchor identity, resolved ARIA relationships and tooltip label references now share one observer per root instead of one per instance, the modal inert pass ignores structural mutations outside the allowed paths, native accessor lookups are memoized, catalog-picker rows are frozen copies, and `CSS_NUMBER_SOURCE` is the one CSS number grammar for the internal length, breakpoint and time parsers.
68
- - fe2bb4e: `lr-dock-panel` keeps focus on the panel itself when content collapses a panel that has no collapse toggle, instead of dropping it to `<body>`. `lr-accordion` skips the availability re-check for ancestor inline-style writes that touch no visibility declaration.
69
- - fe2bb4e: `lr-random-content` now shares the label observer used by the other label-reading components, including its `aria-labelledby` tracking, and `lr-export-button` shares the popup outside-press binding. Chart legend-position styles are shared between `lr-chart` and `lr-box-plot`. No API change.
70
- - 902c90b: `lr-option`'s `title` now reaches the option row rendered by `lr-select` and `lr-combobox` (and the multiple-mode tag) as a tooltip, without changing the accessible name; `ComboboxSourceRow` accepts a `title` too. `lr-filter-bar` slotted `end` actions now reserve the same trailing validation spacer as the fields and Reset, so they share the field frames' bottom edge, and Reset and the end actions wrap together as one group.
71
- - fe2bb4e: Components no longer read their own shadow root's focused element while disconnected. A Lit update queued before a host was removed now skips focus bookkeeping instead of reading `activeElement` on a detached host, which threw under Happy DOM and surfaced as an unhandled rejection.
72
- - fe2bb4e: The passive SVG sanitizer now refuses SVG whose nested `<use>` references fan out past a rendering ceiling, so `<lr-notebook-viewer>` SVG outputs and `<lr-include>` content get the same protection as `<lr-svg-viewer>`. EPUB and PPTX archive guards now node-count package, content and slide parts reached through the container, manifest and relationships under any file name, as the DOCX guard already did. `<lr-ebook-viewer>` keeps keyboard focus when its chapter buttons disable, and `<lr-html-viewer>`, `<lr-contact-viewer>` and `<lr-calendar-viewer>` search only loaded content, never their empty-state, spinner or error text.
73
- - Updated dependencies [fe2bb4e]
74
- - @aceshooting/lyra-translations@27.0.0
55
+ - 3160025: Components that derive an accessible name from slotted content read computed style about half as often while they observe it. Each rebind now reuses the composed-ancestor readings its own text walk already took for the ancestor visibility baselines, instead of a second `getComputedStyle()` pass over every ancestor, and a mutation batch reads each element once however many records it carries. Behaviour is unchanged.
56
+ - 9767b43: Bundle budgets: the v28 document token layer, the emoji peer diagnostics, the forwarded table pager options and the shared accessible-text readings raise every breached JavaScript ceiling to exactly its measured gzip size (for example `dist/all.js` 1,259,466 B, `dist/lyra.js` 134,821 B, the component p95 128,585 B and maximum 255,947 B), with no headroom. `theme.css` and its compositions now carry the token layer (`css/theme` 13,508 B measured, after writing each mode pair around only the tokens that differ); their CSS ceilings follow the CSS budget file's mandatory derivation from the reviewed bytes.
57
+
58
+ The surface-derived dark Glass anchor adds at most 26 gzip bytes to any bundle that carries the Glass fill; the affected ceilings follow to their measured bytes, again with no headroom.
59
+
60
+ The configurable dark Glass share and the table and virtual-list overlay raise rule add a few gzip bytes to the bundles that carry them; their failing ceilings move to the exact measured bytes, again with no headroom.
61
+ - 3e9e251: Docs: the native chrome guide now explains that an unlayered application `background` rule on an `.lr-surface-chrome` element beats Lyra's layered Glass fill, and shows where application fallback fills belong: a low-priority layer declared before Lyra's (`@layer app-base, lr-base, lr-theme, lr-theme-preset, lr-utilities, lr-overrides;`) or the `--lr-surface-background` token.
62
+ - b6636b8: Dark Glass chrome now follows the theme's surface ramp. Below 70% opacity the fill is deepened toward a darkened `--lr-color-surface` (10% of the surface, the rest black) instead of pure black, so a consumer who lifts the dark surface tokens (for example a neutral grey `--lr-theme-color-surface-default`, `-raised` and `-overlay`) gets chrome that follows them rather than staying pure-black-anchored. The share is small because stock palettes are qualified for 4.5:1 text over a white backdrop at 60% opacity; set `--lr-theme-surface-opacity` to 0.7 or higher to paint the palette's own overlay colour. Stock palettes move by at most about 3 levels per channel.
63
+ - c4a8e96: `<lr-image-viewer>` region highlights now paint exactly the percentage rectangle they describe. A region smaller than `--lr-icon-button-size` no longer grows down and right past its data and over its neighbours at any fit, zoom or rotation. The minimum pointer target is now a transparent `::after` centred on the highlight, painted beneath every highlight box, so a region's own box always wins over a neighbour's enlarged area and overlapping enlarged areas resolve to the later highlight in document order. Keyboard navigation is unchanged.
64
+ - 081312e: `lr-table`: an anchored overlay (`lr-dropdown`, `lr-popover`, `lr-select`, `lr-combobox`) inside a `sticky` column cell, such as a per-row actions menu, is no longer covered by the next row's sticky cell. The cell holding an open overlay is raised to the popover layer and a cell with focus inside is raised one step above content, so the open panel stays visible and its items stay clickable over the following rows. `lr-virtual-list` shares the same raise rule and now also covers an open `lr-popover`, `lr-select` or `lr-combobox` in a row. The sticky docs describe how overlays behave in sticky cells and when `top-layer` is still the right opt-in.
65
+ - bbcd486: Avoid allocating a deferred Chromium root observer when the token layer and its root key are adopted immediately. Documents without a static token stylesheet no longer retain an unnecessary observer; documents with an existing stylesheet still defer the key until the root changes.
66
+
67
+ The final 28.0.0 version stamp changes compressed split-chunk routes: CDN autoloader 13,116 bytes, autoloader 12,935 bytes, scoped-registry loader 11,295 bytes, and tool-call-chip initial marginal 7,620 bytes. Their tight ceilings advance by 1 to 3 bytes to those exact measurements, with no added headroom; shell imports, peer exclusions, and passing ceilings are unchanged.
68
+ - 8147014: Preserve Date, Map, and Set values when owned collections cross another property or event boundary, and keep synchronous file-tree child updates from overwriting one another. Honor inherited and non-enumerable Intl formatting options.
69
+
70
+ Correct rubric validation for prototype-named keys, duplicate category choices, and extreme finite score ranges. Cancel pending color eyedropper results when values, form state, or interaction availability change.
71
+
72
+ Release failed or superseded PDF loading tasks, bound page-rail thumbnail bookkeeping to mounted rows, and cancel or reuse QR logo loads. Refresh chart colors when their CSS variables change, bound color caches, and reduce chart synchronization and virtual-grid preprocessing. Refresh offscreen grid measurements after caller-owned row edits. Restore image highlight hover transitions while keeping region geometry updates immediate.
73
+
74
+ Split default English strings into physical family/shared modules and specialist tokens into a separate payload for smaller native ESM downloads. Exclude unused tooling token records from the published package.
75
+
76
+ Review the resulting JavaScript delivery costs: Intl option capture, repeated collection snapshots, cancellable QR/PDF resources, and bounded chart/viewer bookkeeping increase some direct and initial-route bundles. Advance only breached ceilings to the next 64-byte boundary (at most 63 bytes of allowance), preserving passing canaries, peer exclusions, the representative shell, and all CSS ceilings. Including the zoom-aware image highlight limits, the complete registration bundle measures 1,265,430 gzip bytes; its ceiling is 1,265,472 bytes.
77
+ - 95f07d1: Keep thin image highlights within their declared regions when zoomed in or out, including in WebKit. Share zoom normalization with the pan-zoom frame and calculate border limits from the measured image size without requiring newer CSS features.
78
+ - @aceshooting/lyra-translations@28.0.0
75
79
 
76
80
  Older major versions: [release history archive](https://github.com/aceshooting/lyra-ui/tree/main/docs/changelog).
package/README.md CHANGED
@@ -239,9 +239,11 @@ density, mode and accent independently ([guide](./llms/shared/styles-and-tokens.
239
239
 
240
240
  `setLyraStyle({})` from `@aceshooting/lyra-ui/theme.js` restores saved choices (missing fields use the built-in
241
241
  profile); `setLyraStyle({ look: "lyra", surface: "solid", accent: null })` selects the earlier appearance and
242
- `resetLyraStyle()` restores the built-in profile. The resolved `--lr-color-*`/`--lr-space-*`/... layer exists only
243
- on each `lr-*` shadow `:host`, so retheme through the `--lr-theme-*` inputs, for example
244
- `:root { --lr-theme-color-brand-fill-loud: #60a5fa; }` ([where an override reaches](./llms/shared.md#where-an-override-actually-reaches),
242
+ `resetLyraStyle()` restores the built-in profile. Since 28.0.0 the resolved `--lr-color-*`/`--lr-space-*`/... layer is
243
+ declared once per document and re-derived only at **theme scopes** (`.lr-light`/`.lr-dark`, `data-lr-theme`, style-axis
244
+ boundaries, or any element marked `data-lr-theme-scope`), so retheme through the `--lr-theme-*` inputs on `:root` or a
245
+ theme scope, for example `:root { --lr-theme-color-brand-fill-loud: #60a5fa; }`; an input on a plain wrapper no longer
246
+ re-derives components below it ([theme scopes](./llms/shared.md#theme-scopes), `lyra-ui-migrate --rule=theme-scopes`) ([where an override reaches](./llms/shared.md#where-an-override-actually-reaches),
245
247
  [tokens](./llms/tokens.md)). `theme.css` declares `@layer lr-base, lr-theme, lr-theme-preset, lr-utilities, lr-overrides`,
246
248
  so an unlayered rule wins (name all five if you declare the order). Canvas components repaint on observable theme
247
249
  changes; otherwise call `invalidateLyraTheme(root?)` from `@aceshooting/lyra-ui/utilities/theme.js`. Optional
@@ -283,6 +285,8 @@ In the browser, make `@aceshooting/lyra-ui/hydration.js` the first import that c
283
285
  `diagnoseLyraHydration(document)` checks the result. A fallback renderer cannot serialize property bindings, so
284
286
  pass initial state as attributes or light DOM. Measurement, observers, canvas and media start after hydration; remote
285
287
  content stays client-only ([details](./llms/shared/frameworks-and-ssr.md#ssr-and-declarative-shadow-dom)).
288
+ Declarative shadow roots don't carry the token layer: link `@aceshooting/lyra-ui/theme.css` (which includes it) or,
289
+ without `theme.css`, `@aceshooting/lyra-ui/tokens-root.css` in `<head>` for a correct first paint.
286
290
 
287
291
  ## Framework integration (React, Vue, Angular, Svelte)
288
292
 
@@ -0,0 +1 @@
1
+ function stripComments(text){let output="",index=0,quote="";for(;index<text.length;){const character=text[index];if(quote){if(output+=character,character==="\\"){output+=text[index+1]??"",index+=2;continue}character===quote&&(quote=""),index+=1;continue}if(character==='"'||character==="'"){quote=character,output+=character,index+=1;continue}if(character==="/"&&text[index+1]==="*"){const end=text.indexOf("*/",index+2),stop=end<0?text.length:end+2;output+=" ".repeat(stop-index),index=stop;continue}output+=character,index+=1}return output}function splitTopLevel(text,separator){const parts=[];let depth=0,quote="",start=0;for(let index=0;index<text.length;index++){const character=text[index];if(quote){character==="\\"?index+=1:character===quote&&(quote="");continue}character==='"'||character==="'"?quote=character:character==="("||character==="["?depth+=1:character===")"||character==="]"?depth-=1:character===separator&&depth===0&&(parts.push(text.slice(start,index)),start=index+1)}return parts.push(text.slice(start)),parts}function readDeclarations(css){const text=stripComments(css),declarations=[],stack=[];let buffer="",quote="",depth=0;const flushDeclaration=()=>{const chunk=buffer.trim();if(buffer="",!chunk)return;const colon=chunk.indexOf(":");if(colon<=0)return;const property=chunk.slice(0,colon).trim();let value=chunk.slice(colon+1).trim();const important=/!\s*important\s*$/i.test(value);important&&(value=value.replace(/!\s*important\s*$/i,"").trim());const rules=stack.filter(entry=>entry.kind==="rule");declarations.push({property,value,important,selector:rules.length?rules[rules.length-1].prelude:"",atRules:stack.filter(entry=>entry.kind==="at").map(entry=>entry.prelude)})};for(let index=0;index<text.length;index++){const character=text[index];if(quote){buffer+=character,character==="\\"?(buffer+=text[index+1]??"",index+=1):character===quote&&(quote="");continue}if(character==='"'||character==="'")quote=character,buffer+=character;else if(character==="(")depth+=1,buffer+=character;else if(character===")")depth-=1,buffer+=character;else if(character==="{"&&depth===0){const prelude=buffer.trim().replace(/\s+/g," ");buffer="",stack.push({kind:prelude.startsWith("@")?"at":"rule",prelude})}else character==="}"&&depth===0?(flushDeclaration(),stack.pop()):character===";"&&depth===0?stack.length&&stack[stack.length-1].kind==="rule"?flushDeclaration():buffer="":buffer+=character}return declarations}export{readDeclarations,splitTopLevel};
@@ -1 +1 @@
1
- const LYRA_RENAME_LEDGER_SCHEMA_VERSION=1,UNRELEASED_VERSION="unreleased",LYRA_RENAME_PROFILES=Object.freeze([Object.freeze({origin:"lyra-v21",fromMajor:21,toMajor:22,aliasRemovalMajor:23}),Object.freeze({origin:"lyra-v22",fromMajor:22,toMajor:23,aliasRemovalMajor:24}),Object.freeze({origin:"lyra-v25",fromMajor:25,toMajor:26,aliasRemovalMajor:28}),Object.freeze({origin:"lyra-v26",fromMajor:26,toMajor:27,aliasRemovalMajor:29})]),LYRA_RENAME_ORIGINS=Object.freeze(LYRA_RENAME_PROFILES.map(profile=>profile.origin)),RENAME_KINDS=Object.freeze(["attribute","property","event","part","css-property","slot"]),REVIEW_KINDS=Object.freeze(["component","attribute","property","event","slot","part","css-property","css-state","method"]),EXPOSURE_KINDS=Object.freeze(["event","part","css-property"]),SURFACE_SECTIONS=Object.freeze({attribute:"attributes",property:"properties",event:"events",part:"parts","css-property":"cssProperties",slot:"slots","css-state":"cssStates",method:"methods"}),TAG_PATTERN=/^lr-[a-z0-9]+(?:-[a-z0-9]+)*$/,ELEMENT_PATTERN=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/,JS_IDENTIFIER_PATTERN=/^[A-Za-z_$][\w$]*$/,VERSION_PATTERN=/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/,NAME_PATTERNS=Object.freeze({component:TAG_PATTERN,attribute:/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/,property:JS_IDENTIFIER_PATTERN,method:JS_IDENTIFIER_PATTERN,event:/^lr-[a-z0-9]+(?:-[a-z0-9]+)*$/,part:/^[A-Za-z0-9_-]+$/,slot:/^(?:[A-Za-z0-9_-]+)?$/,"css-property":/^--[A-Za-z0-9_-]+$/,"css-state":/^[a-z][a-z0-9-]*$/}),MODULE_REVIEW_KINDS=["entry-point","stylesheet","function","type","constant","class","window-event","root-attribute"],MODULE_PATH_PATTERN=/^\.\/[A-Za-z0-9_./-]+\.(?:js|css)$/,moduleReviewKey=entry=>[entry.kind,entry.module??"",entry.name].join("\0"),PROFILE_LIST_KEYS=["renames","defaults","detailChanges","detailFields","propertyChanges","retiredEvents","reviews","slotContent","moduleReviews","globals"],OPTIONAL_PROFILE_LISTS=["propertyChanges","retiredEvents","detailFields","globals"],AUTHORED_PROFILE_KEYS=["origin","fromMajor","toMajor","aliasRemovalMajor",...PROFILE_LIST_KEYS],GLOBAL_KINDS=Object.freeze(["module","export","locale-key","css-property","part"]),PACKAGE_SPECIFIER_PATTERN=/^@aceshooting\/lyra-[a-z]+(?:\/[A-Za-z0-9_./-]*)?$/,PROJECTED_PROFILE_KEYS=[...AUTHORED_PROFILE_KEYS,"exposure"];function isPlainObject(value){return value!==null&&typeof value=="object"&&!Array.isArray(value)}function unknownKeys(value,allowed){return Object.keys(value).filter(key=>!allowed.includes(key))}function compareText(left,right){return left<right?-1:left>right?1:0}function parseVersion(version){const match=VERSION_PATTERN.exec(String(version??""));return match?match.slice(1,4).map(Number):null}function compareVersions(left,right){const a=parseVersion(left),b=parseVersion(right);if(!a||!b)return null;for(let index=0;index<3;index+=1)if(a[index]!==b[index])return a[index]-b[index];return 0}function majorOf(version){return parseVersion(version)?.[0]??null}function emptyRenameLedger(){return{schemaVersion:1,profiles:LYRA_RENAME_PROFILES.map(header=>({...header,...Object.fromEntries(PROFILE_LIST_KEYS.filter(list=>!["detailFields","globals"].includes(list)).map(list=>[list,[]]))}))}}function emptyRenameProjection(){return{schemaVersion:1,profiles:emptyRenameLedger().profiles.map(profile=>({...profile,exposure:Object.fromEntries(EXPOSURE_KINDS.map(kind=>[kind,{}]))}))}}function entryLabel(origin,list,entry){return list==="globals"?`${origin}: global ${entry?.kind} ${entry?.from}`:list==="moduleReviews"?`${origin}: module review ${entry?.kind} ${entry?.module??""} ${entry?.name}`:list==="renames"?`${origin}: rename ${entry?.tag} ${entry?.kind} ${entry?.from}`:list==="defaults"?`${origin}: default ${entry?.tag} ${entry?.attribute}`:list==="retiredEvents"?`${origin}: retired event ${entry?.tag} ${entry?.event}`:list==="detailChanges"?`${origin}: detail change ${entry?.tag} ${entry?.event}`:list==="detailFields"?`${origin}: detail field ${entry?.tag} ${entry?.event} ${entry?.detailType}.${entry?.field}`:list==="propertyChanges"?`${origin}: property change ${entry?.tag} ${entry?.property}`:list==="slotContent"?`${origin}: slot content ${entry?.tag} ${JSON.stringify(entry?.slot??null)}`:`${origin}: review ${entry?.tag} ${entry?.kind} ${JSON.stringify(entry?.name??null)}`}function sortKey(list,entry){return list==="moduleReviews"?moduleReviewKey(entry):list==="globals"?[entry.kind,entry.from,entry.tag??""].join("\0"):list==="renames"?[entry.tag,entry.kind,entry.from].join("\0"):list==="defaults"?[entry.tag,entry.attribute].join("\0"):list==="detailChanges"||list==="retiredEvents"?[entry.tag,entry.event].join("\0"):list==="detailFields"?[entry.tag,entry.event,entry.module,entry.detailType,entry.field].join("\0"):list==="propertyChanges"?[entry.tag,entry.property].join("\0"):list==="slotContent"?[entry.tag,entry.slot].join("\0"):[entry.tag,entry.kind,entry.name].join("\0")}function validateName(findings,label,kind,field,value){return typeof value!="string"||!NAME_PATTERNS[kind]?.test(value)?(findings.push(`${label}: ${field} must be a valid ${kind} name, got ${JSON.stringify(value)}`),!1):!0}function validateProjectedSince(findings,label,entry,projected){projected&&entry.since!==UNRELEASED_VERSION&&!parseVersion(entry.since)&&findings.push(`${label}: since must be a version or ${JSON.stringify(UNRELEASED_VERSION)}`)}function validateRemovedIn(findings,label,entry){Object.hasOwn(entry,"removedIn")&&(!parseVersion(entry.removedIn)||compareVersions(entry.removedIn,entry.removalNotBefore)<0)&&findings.push(`${label}: removedIn must be a version at or after removalNotBefore`)}function validateModuleReviewEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,["kind","module","name",...projected?["replacement","since","removalNotBefore","removedIn"]:[]]);if(unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),MODULE_REVIEW_KINDS.includes(entry.kind)||findings.push(`${label}: unsupported module review kind ${entry.kind}`),entry.kind==="entry-point"||entry.kind==="stylesheet")Object.hasOwn(entry,"module")&&findings.push(`${label}: path reviews use name, not module`),typeof entry.name!="string"||!MODULE_PATH_PATTERN.test(entry.name)||entry.name.includes("..")?findings.push(`${label}: name must be a package subpath`):entry.name.endsWith(".css")!==(entry.kind==="stylesheet")&&findings.push(`${label}: stylesheet reviews must name CSS; entry-point reviews must name JavaScript`);else{entry.module!=="."&&(typeof entry.module!="string"||!MODULE_PATH_PATTERN.test(entry.module)||!entry.module.endsWith(".js")||entry.module.includes(".."))&&findings.push(`${label}: module must be a JavaScript package subpath`);const pattern=entry.kind==="window-event"?NAME_PATTERNS.event:entry.kind==="root-attribute"?/^data-lr-[a-z0-9-]+$/:JS_IDENTIFIER_PATTERN;(typeof entry.name!="string"||!pattern.test(entry.name))&&findings.push(`${label}: invalid module review name`)}projected&&((typeof entry.replacement!="string"||!entry.replacement.trim())&&findings.push(`${label}: replacement text missing`),parseVersion(entry.removalNotBefore)||findings.push(`${label}: removalNotBefore must be a version`),validateRemovedIn(findings,label,entry)),validateProjectedSince(findings,label,entry,projected)}function validateGlobalEntry(findings,label,entry){const unknown=unknownKeys(entry,["kind","from","to","module","prefix","except","tag","summary","since"]);if(unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),!GLOBAL_KINDS.includes(entry.kind)){findings.push(`${label}: kind must be one of ${GLOBAL_KINDS.join(", ")}`);return}(typeof entry.summary!="string"||!entry.summary.trim())&&findings.push(`${label}: summary text missing`),parseVersion(entry.since)||findings.push(`${label}: since must be a version`);const optional=entry.kind==="locale-key";if(entry.kind==="module"){const valid=value=>typeof value=="string"&&PACKAGE_SPECIFIER_PATTERN.test(value)&&!value.includes("..");if(valid(entry.from)||findings.push(`${label}: from must be an @aceshooting package specifier`),valid(entry.to)||findings.push(`${label}: to must be an @aceshooting package specifier`),valid(entry.from)&&entry.from===entry.to&&findings.push(`${label}: from and to must differ`),Object.hasOwn(entry,"module")&&findings.push(`${label}: module entries do not take a module`),Object.hasOwn(entry,"prefix")&&entry.prefix!==!0&&findings.push(`${label}: prefix may only be true`),entry.prefix===!0&&!(entry.from?.endsWith("/")&&entry.to?.endsWith("/"))&&findings.push(`${label}: a prefix entry's from and to must end with a slash`),Object.hasOwn(entry,"except")){const exceptions=entry.except;entry.prefix!==!0||!Array.isArray(exceptions)||!exceptions.length||!exceptions.every(value=>typeof value=="string"&&value.startsWith(entry.from)&&value.endsWith("/"))?findings.push(`${label}: except needs prefix and a list of slash-terminated prefixes below from`):JSON.stringify(exceptions)!==JSON.stringify([...exceptions].sort(compareText))&&findings.push(`${label}: except must be sorted`)}return}if(entry.kind==="css-property"||entry.kind==="part"){(typeof entry.to!="string"||!entry.to.trim())&&findings.push(`${label}: to must name the replacement`),(Object.hasOwn(entry,"module")||Object.hasOwn(entry,"except"))&&findings.push(`${label}: ${entry.kind} entries take no module or except`),entry.kind==="part"?((typeof entry.tag!="string"||!TAG_PATTERN.test(entry.tag))&&findings.push(`${label}: tag must be an lr-* element name`),Object.hasOwn(entry,"prefix")&&findings.push(`${label}: prefix applies to custom properties only`),validateName(findings,label,"part","from",entry.from)):(Object.hasOwn(entry,"tag")&&findings.push(`${label}: custom properties are not tag-owned`),validateName(findings,label,"css-property","from",entry.from),Object.hasOwn(entry,"prefix")&&(entry.prefix!==!0||!entry.from.endsWith("-"))&&findings.push(`${label}: a prefix needs prefix: true and a name ending in a hyphen`));return}for(const field of["prefix","except","tag"])Object.hasOwn(entry,field)&&findings.push(`${label}: ${field} applies to module, part and custom property entries only`);(typeof entry.from!="string"||!JS_IDENTIFIER_PATTERN.test(entry.from))&&findings.push(`${label}: from must be an identifier`),(typeof entry.to!="string"||!JS_IDENTIFIER_PATTERN.test(entry.to))&&findings.push(`${label}: to must be an identifier`),entry.from===entry.to&&findings.push(`${label}: from and to must differ`),entry.kind==="export"&&entry.module!=="."&&findings.push(`${label}: an export entry needs module "." (the package root)`),optional&&Object.hasOwn(entry,"module")&&findings.push(`${label}: locale keys do not take a module`)}function validateRenameEntry(findings,label,entry,projected){const allowed=["tag","kind","from","to","polarity",...projected?["since","reflects"]:[]],unknown=unknownKeys(entry,allowed);if(unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),!RENAME_KINDS.includes(entry.kind)){findings.push(`${label}: kind must be one of ${RENAME_KINDS.join(", ")}`);return}const validFrom=validateName(findings,label,entry.kind,"from",entry.from),validTo=validateName(findings,label,entry.kind,"to",entry.to);validFrom&&validTo&&entry.from===entry.to&&findings.push(`${label}: from and to must differ`),entry.kind==="slot"&&(entry.from===""||entry.to==="")&&findings.push(`${label}: the default slot can be reviewed but not renamed`),Object.hasOwn(entry,"polarity")&&(entry.polarity!=="inverted"&&findings.push(`${label}: polarity may only be "inverted"`),entry.kind!=="attribute"&&entry.kind!=="property"&&findings.push(`${label}: only attribute and property renames can invert polarity`)),validateProjectedSince(findings,label,entry,projected),projected&&Object.hasOwn(entry,"reflects")!==(entry.kind==="attribute")&&findings.push(`${label}: reflects is recorded for attribute renames only`),projected&&entry.kind==="attribute"&&typeof entry.reflects!="boolean"&&findings.push(`${label}: reflects must be a boolean`)}function validateDefaultEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,["tag","attribute","value",...projected?["since"]:[]]);unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),validateName(findings,label,"attribute","attribute",entry.attribute),Object.hasOwn(entry,"value")?typeof entry.value!="string"&&entry.value!==!0&&!(typeof entry.value=="number"&&Number.isFinite(entry.value))&&findings.push(`${label}: value must be a string, a finite number, or true (presence)`):findings.push(`${label}: needs a value`),validateProjectedSince(findings,label,entry,projected)}function validateSummary(findings,label,summary){(typeof summary!="string"||summary.trim().length<16||/\n/.test(summary))&&findings.push(`${label}: summary must be a single-line description of the change`)}function validateDetailEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,["tag","event","summary",...projected?["since"]:[]]);unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),validateName(findings,label,"event","event",entry.event),validateSummary(findings,label,entry.summary),validateProjectedSince(findings,label,entry,projected)}function validateDetailFieldEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,["tag","event","module","detailType","field","relation","replacement","role","notice","removalNotBefore","observedIn","removedIn"]);if(unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),validateName(findings,label,"event","event",entry.event),(typeof entry.module!="string"||!/^(?:layout|conversation)\/[a-z0-9-]+\/[a-z0-9-]+(?:\.class|-shared)\.ts$/u.test(entry.module))&&findings.push(`${label}: module must be a canonical layout or conversation detail source path`),(typeof entry.detailType!="string"||!JS_IDENTIFIER_PATTERN.test(entry.detailType))&&findings.push(`${label}: detailType must be an exported type name`),(typeof entry.field!="string"||!JS_IDENTIFIER_PATTERN.test(entry.field))&&findings.push(`${label}: field must be a property name`),["equal","inverse"].includes(entry.relation)||findings.push(`${label}: relation must be equal or inverse`),entry.replacement!=="expanded"&&findings.push(`${label}: replacement must be expanded`),(!["request","settled"].includes(entry.role)||typeof entry.event!="string"||entry.role==="request"!==entry.event.endsWith("-request"))&&findings.push(`${label}: role must match whether the event is a request event`),(typeof entry.notice!="string"||!/^\/\*\*[\s\S]*@deprecated\b[\s\S]*\*\/$/u.test(entry.notice))&&findings.push(`${label}: notice must preserve the exact deprecation JSDoc`),entry.removalNotBefore!=="23.0.0"&&findings.push(`${label}: removalNotBefore must preserve the declared 23.0.0 floor`),entry.observedIn!=="22.0.0"&&findings.push(`${label}: observedIn must name the earliest verified packed observation, 22.0.0`),projected&&typeof entry.observedIn=="string"&&!parseVersion(entry.observedIn)&&findings.push(`${label}: observedIn must be a semantic version`),entry.removedIn!==void 0&&entry.removedIn!=="24.0.0"&&findings.push(`${label}: removedIn requires the separately authorized actual 24.0.0 retirement`),entry.removedIn!==void 0){const removal=parseVersion(entry.removedIn),floor=parseVersion(entry.removalNotBefore),observed=parseVersion(entry.observedIn);(!removal||!floor||!observed||removal[0]<floor[0]+1||removal[0]<observed[0]+2)&&findings.push(`${label}: removedIn must clear the declared floor and a full intervening published major`)}}function validateRetiredEventEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,["tag","event","replacement","summary",...projected?["since"]:[]]);unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),validateName(findings,label,"event","event",entry.event),validateName(findings,label,"event","replacement",entry.replacement),entry.event===entry.replacement&&findings.push(`${label}: replacement must differ from the retired event`),validateSummary(findings,label,entry.summary),validateProjectedSince(findings,label,entry,projected)}function validatePropertyChangeEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,["tag","property","former","summary",...projected?["since"]:[]]);unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),validateName(findings,label,"property","property",entry.property),Object.hasOwn(entry,"former")&&validateName(findings,label,"property","former",entry.former)&&entry.former===entry.property&&findings.push(`${label}: former must differ from property`),validateSummary(findings,label,entry.summary),validateProjectedSince(findings,label,entry,projected)}function validateSlotContentEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,["tag","slot","report","allow","summary",...projected?["since"]:[]]);unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),validateName(findings,label,"slot","slot",entry.slot);const lists=["report","allow"].filter(key=>Object.hasOwn(entry,key));if(lists.length!==1)findings.push(`${label}: needs exactly one of report (elements to report) or allow (the only elements kept)`);else{const elements=entry[lists[0]];(!Array.isArray(elements)||!elements.length||!elements.every(element=>typeof element=="string"&&ELEMENT_PATTERN.test(element))||JSON.stringify(elements)!==JSON.stringify([...new Set(elements)].sort(compareText)))&&findings.push(`${label}: ${lists[0]} must be a sorted, unique list of lowercase element names`)}validateSummary(findings,label,entry.summary),validateProjectedSince(findings,label,entry,projected)}function validateReviewEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,projected?["tag","kind","name","replacement","removalNotBefore","since","removedIn"]:["tag","kind","name"]);if(unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),!REVIEW_KINDS.includes(entry.kind)){findings.push(`${label}: kind must be one of ${REVIEW_KINDS.join(", ")}`);return}validateName(findings,label,entry.kind,"name",entry.name)&&entry.kind==="component"&&entry.name!==entry.tag&&findings.push(`${label}: a component review must name its own tag`),projected&&((typeof entry.replacement!="string"||!entry.replacement)&&findings.push(`${label}: replacement text missing`),majorOf(entry.removalNotBefore)===null&&findings.push(`${label}: removalNotBefore must be a version`),validateRemovedIn(findings,label,entry)),validateProjectedSince(findings,label,entry,projected)}function requiredExposure(profile){const required=new Map(EXPOSURE_KINDS.map(kind=>[kind,new Map])),need=(kind,name,tag)=>{if(!required.has(kind))return;const tags=required.get(kind).get(name)??new Set;tag&&tags.add(tag),required.get(kind).set(name,tags)};for(const entry of profile.renames??[])need(entry.kind,entry.from,entry.tag),need(entry.kind,entry.to,entry.tag);for(const entry of profile.reviews??[])need(entry.kind,entry.name,entry.tag);for(const entry of profile.detailChanges??[])need("event",entry.event,entry.tag);for(const entry of profile.retiredEvents??[])need("event",entry.event,null),need("event",entry.replacement,entry.tag);for(const entry of profile.propertyChanges??[])need("property",entry.property,entry.tag);return required}function validateExposure(findings,profile){const origin=profile.origin;if(!isPlainObject(profile.exposure)){findings.push(`${origin}: exposure must be an object`);return}const unknown=unknownKeys(profile.exposure,EXPOSURE_KINDS);unknown.length&&findings.push(`${origin}: exposure has unknown kind(s) ${unknown.join(", ")}`);const required=requiredExposure(profile);for(const kind of EXPOSURE_KINDS){const recorded=profile.exposure[kind];if(!isPlainObject(recorded)){findings.push(`${origin}: exposure.${kind} must be an object`);continue}for(const[name,tags]of Object.entries(recorded))required.get(kind).has(name)||findings.push(`${origin}: exposure.${kind} records unreferenced name ${name}`),(!Array.isArray(tags)||!tags.every(tag=>typeof tag=="string"&&TAG_PATTERN.test(tag))||JSON.stringify(tags)!==JSON.stringify([...new Set(tags)].sort(compareText)))&&findings.push(`${origin}: exposure.${kind}.${name} must be a sorted, unique tag list`);for(const[name,tags]of required.get(kind)){if(!Object.hasOwn(recorded,name)){findings.push(`${origin}: exposure.${kind}.${name} is missing`);continue}const recordedTags=new Set(recorded[name]??[]);for(const tag of tags)recordedTags.has(tag)||findings.push(`${origin}: exposure.${kind}.${name} must include ${tag}`)}}}function validateRenameLedgerShape(ledger,{projected=!1,historicalReleaseMajor=null}={}){const findings=[];if(!isPlainObject(ledger))return["rename ledger must be an object"];const unknownTop=unknownKeys(ledger,projected?["schemaVersion","profiles"]:["$comment","schemaVersion","profiles"]);if(unknownTop.length&&findings.push(`rename ledger has unknown key(s) ${unknownTop.join(", ")}`),ledger.schemaVersion!==1&&findings.push("rename ledger schemaVersion must be 1"),!Array.isArray(ledger.profiles))return[...findings,"rename ledger profiles must be an array"];const origins=ledger.profiles.map(profile=>profile?.origin),historicalCount=!Number.isInteger(historicalReleaseMajor)||historicalReleaseMajor<22?0:historicalReleaseMajor<26?2:historicalReleaseMajor<27?3:0,expectedOrigins=historicalCount&&JSON.stringify(origins)===JSON.stringify(LYRA_RENAME_ORIGINS.slice(0,historicalCount))?LYRA_RENAME_ORIGINS.slice(0,historicalCount):LYRA_RENAME_ORIGINS;JSON.stringify(origins)!==JSON.stringify(expectedOrigins)&&findings.push(`rename ledger profiles must be exactly ${expectedOrigins.join(", ")} in that order`);for(const profile of ledger.profiles){if(!isPlainObject(profile)){findings.push("rename ledger profiles must be objects");continue}const origin=String(profile.origin),unknown=unknownKeys(profile,projected?PROJECTED_PROFILE_KEYS:AUTHORED_PROFILE_KEYS);unknown.length&&findings.push(`${origin}: unknown key(s) ${unknown.join(", ")}`);const header=LYRA_RENAME_PROFILES.find(candidate=>candidate.origin===profile.origin);if(header)for(const field of["fromMajor","toMajor","aliasRemovalMajor"])profile[field]!==header[field]&&findings.push(`${origin}: ${field} must be ${header[field]}`);for(const list of PROFILE_LIST_KEYS){if(OPTIONAL_PROFILE_LISTS.includes(list)&&profile[list]===void 0)continue;if(!Array.isArray(profile[list])){findings.push(`${origin}: ${list} must be an array`);continue}const seen=new Set,keys=[];for(const entry of profile[list]){const label=entryLabel(origin,list,entry);if(!isPlainObject(entry)){findings.push(`${origin}: ${list} entries must be objects`);continue}list!=="moduleReviews"&&list!=="globals"&&(typeof entry.tag!="string"||!TAG_PATTERN.test(entry.tag))&&findings.push(`${label}: tag must be an lr-* element name`),list==="globals"?validateGlobalEntry(findings,label,entry):list==="moduleReviews"?(validateModuleReviewEntry(findings,label,entry,projected),projected&&majorOf(entry.removalNotBefore)!==profile.aliasRemovalMajor&&findings.push(`${label}: removalNotBefore must match the profile's aliasRemovalMajor`)):list==="renames"?validateRenameEntry(findings,label,entry,projected):list==="defaults"?validateDefaultEntry(findings,label,entry,projected):list==="detailChanges"?validateDetailEntry(findings,label,entry,projected):list==="detailFields"?validateDetailFieldEntry(findings,label,entry,projected):list==="retiredEvents"?(validateRetiredEventEntry(findings,label,entry,projected),projected&&entry.since!==`${profile.toMajor}.0.0`&&findings.push(`${label}: since must match the target major release`)):list==="propertyChanges"?validatePropertyChangeEntry(findings,label,entry,projected):list==="slotContent"?validateSlotContentEntry(findings,label,entry,projected):validateReviewEntry(findings,label,entry,projected);const key=sortKey(list,entry);seen.has(key)&&findings.push(`${label}: duplicate entry`),seen.add(key),keys.push(key)}JSON.stringify(keys)!==JSON.stringify([...keys].sort(compareText))&&findings.push(`${origin}: ${list} must be sorted by ${list==="moduleReviews"?"kind, module and name":list==="globals"?"kind and name":"tag and name"}`)}if(!Array.isArray(profile.renames)||!Array.isArray(profile.reviews))continue;const sources=new Map;for(const entry of profile.renames){const names=sources.get(entry.kind)??new Set;names.add(entry.from),sources.set(entry.kind,names)}for(const entry of profile.renames)sources.get(entry.kind)?.has(entry.to)&&findings.push(`${entryLabel(origin,"renames",entry)}: target ${entry.to} is itself renamed; rename directly to the final name`);const renamed=new Set(profile.renames.map(entry=>`${entry.tag}\0${entry.kind}\0${entry.from}`));for(const entry of profile.reviews)renamed.has(`${entry.tag}\0${entry.kind}\0${entry.name}`)&&findings.push(`${entryLabel(origin,"reviews",entry)}: a member is either renamed or reviewed, not both`);projected&&validateExposure(findings,profile)}if(ledger.profiles.length>=2){const historicalFields=ledger.profiles[0]?.detailFields,nextFields=ledger.profiles[1]?.detailFields;(Array.isArray(historicalFields)||Array.isArray(nextFields))&&JSON.stringify(historicalFields??[])!==JSON.stringify(nextFields??[])&&findings.push("lyra-v21 and lyra-v22 detailFields must preserve the same verified field exposures")}return findings}function componentMap(inventory){return new Map((inventory?.components??[]).map(component=>[component.tag,component]))}function surfaceEntry(component,kind,name){return(component?.surface?.[SURFACE_SECTIONS[kind]]??[]).find(entry=>entry.name===name)??null}function hasCurrentSuccessor(component,kind,name){const seen=new Set;for(;;){const key=`${kind}\0${name}`;if(seen.has(key))return!1;seen.add(key);const member=surfaceEntry(component,kind,name);if(!member)return!1;if(!member.deprecated)return!0;const replacement=deprecationRecordFor(component,kind,name)?.replacement;if(!replacement||replacement.kind==="component"||replacement.kind==="host-css-property")return!1;kind=replacement.kind,name=replacement.name}}function deprecationRecords(component){const records=[...component?.maturity?.deprecations??[]],componentRecord=component?.maturity?.deprecated;return isPlainObject(componentRecord)&&!records.some(record=>record.kind==="component")&&records.push(componentRecord),records}function deprecationRecordFor(component,kind,name){const records=deprecationRecords(component);if(kind==="component")return records.find(record=>record.kind==="component")??null;const direct=records.find(record=>record.kind===kind&&record.name===name);return direct||kind!=="attribute"?direct??null:records.find(record=>record.kind==="property"&&record.attribute===name)??null}function historicalMember(context,tag,kind,name){const key=JSON.stringify(["member",tag,kind,name]);return context?.records?.[key]??context?.records?.[context?.aliases?.[key]]??null}function historicalSourceComponent(context,tag,record=null){const historical=record??historicalMember(context,tag,"component",tag);return historical?.state!=="retired"?null:historical.sourceComponent??context?.sourceComponents?.[tag]??null}function historicalExport(context,entry){return context?.records?.[JSON.stringify(["export",entry.kind,entry.module??null,entry.name])]??null}function ownerFor(components,context,tag){const current=components.get(tag);return current||historicalSourceComponent(context,tag)}function policyFor(component,context,tag,kind,name){return deprecationRecordFor(component,kind,name)??historicalMember(context,tag,kind,name)?.policy??null}function replacementOwner(components,context,tag,kind,name){const historical=historicalMember(context,tag,kind,name);return components.get(historical?.state==="retired"?historical.replacementOwner:tag)}function replacementMatches(component,kind,record,to){const replacement=record?.replacement;return isPlainObject(replacement)?replacement.kind===kind&&replacement.name===to?!0:kind==="attribute"&&replacement.kind==="property"?surfaceEntry(component,"property",replacement.name)?.attribute===to:!1:!1}function isBooleanMember(entry){return String(entry?.type??"").split("|").map(part=>part.trim()).includes("boolean")}function exposingTags(inventory,kind,name){return(inventory?.components??[]).filter(component=>surfaceEntry(component,kind,name)).map(component=>component.tag).sort(compareText)}const UNMIRRORED_CLASSIFICATIONS=new Set(["conceptual-only","unsupported"]);function mirroredMembers(inventory){const upstreamComponents=new Map;for(const ecosystem of Object.values(inventory?.upstreams??{}))for(const component of ecosystem?.components??[])upstreamComponents.set(component.tag,component);const mirrored=new Map;for(const mapping of inventory?.mappings??[]){if(!mapping?.targetTag||UNMIRRORED_CLASSIFICATIONS.has(mapping.classification))continue;const upstream=upstreamComponents.get(mapping.upstreamTag);for(const[kind,section]of Object.entries(SURFACE_SECTIONS)){const rewrites=mapping.rewrites?.[section]??[],renamedAway=new Set(rewrites.map(rule=>rule?.from)),names=[...rewrites.map(rule=>rule?.to).filter(name=>typeof name=="string"),...(upstream?.surface?.[section]??[]).map(entry=>entry.name).filter(name=>!renamedAway.has(name))];for(const name of names){const key=`${mapping.targetTag}\0${kind}\0${name}`;mirrored.has(key)||mirrored.set(key,mapping.upstreamTag)}}}return mirrored}function validateRenameLedger(ledger,{inventory,exportDeprecations=[],requireCoverage=!1,sharedTokens=null,compatibilityContext=null,historicalReleaseMajor=null}){const findings=validateRenameLedgerShape(ledger,{historicalReleaseMajor});if(findings.length)return findings;const components=componentMap(inventory);if(!Array.isArray(exportDeprecations))return["exportDeprecations must be an array"];if(!exportDeprecations.every(isPlainObject))return["exportDeprecations entries must be objects"];const moduleRecords=new Map(exportDeprecations.map(record=>[moduleReviewKey(record),record]));moduleRecords.size!==exportDeprecations.length&&findings.push("exportDeprecations contains duplicate module records");const mirrored=mirroredMembers(inventory),mirrorOf=(tag,kind,name)=>mirrored.get(`${tag}\0${kind}\0${name}`)??null;for(const profile of ledger.profiles){const origin=profile.origin,covered=new Set,release=`${profile.toMajor}.0.0`,removalMajorMatches=record=>majorOf(record?.removalNotBefore)===profile.aliasRemovalMajor,checkRecordWindow=(label,record)=>{if(removalMajorMatches(record)||findings.push(`${label}: its deprecation record must set removalNotBefore to ${profile.aliasRemovalMajor}.0.0`),record?.since===UNRELEASED_VERSION)return;const since=compareVersions(record?.since,release);(since===null||since>0)&&findings.push(`${label}: its deprecation record must start no later than ${release} (since ${JSON.stringify(record?.since)})`)},checkLyraOnly=(label,tag,kind,name)=>{const upstream=mirrorOf(tag,kind,name);upstream&&findings.push(`${label}: ${name} mirrors ${upstream}; mirrored names never change`)},checkSharedToken=(label,kind,name)=>{kind==="css-property"&&sharedTokens?.has(name)&&findings.push(`${label}: ${name} is a canonical design token, not a component-owned custom property`)},presenceDefaults=new Set(ledger.profiles.filter(candidate=>candidate.toMajor<=profile.toMajor).flatMap(candidate=>candidate.defaults).filter(entry=>entry.value===!0).map(entry=>`${entry.tag}\0${entry.attribute}`)),inversionCompanions=new Set;for(const entry of profile.detailFields??[]){const label=entryLabel(origin,"detailFields",entry),component=ownerFor(components,compatibilityContext,entry.tag);component?surfaceEntry(component,"event",entry.event)||findings.push(`${label}: the event is not dispatched by ${entry.tag}`):findings.push(`${label}: component is not in the inventory`)}for(const entry of profile.renames){const label=entryLabel(origin,"renames",entry),component=ownerFor(components,compatibilityContext,entry.tag);if(!component){findings.push(`${label}: component is not in the inventory`);continue}covered.add(`${entry.tag}\0${entry.kind}\0${entry.from}`),checkLyraOnly(label,entry.tag,entry.kind,entry.from),checkSharedToken(label,entry.kind,entry.from),checkSharedToken(label,entry.kind,entry.to);const historical=historicalMember(compatibilityContext,entry.tag,entry.kind,entry.from),retired=historical?.state==="retired",from=surfaceEntry(retired?historicalSourceComponent(compatibilityContext,entry.tag,historical):component,entry.kind,entry.from),targetOwner=replacementOwner(components,compatibilityContext,entry.tag,entry.kind,entry.from),to=surfaceEntry(targetOwner,entry.kind,entry.to);from?from.deprecated||findings.push(`${label}: the alias ${entry.from} is not marked deprecated in the manifest`):findings.push(`${label}: the deprecated alias ${entry.from} is not on the public surface`),to?to.deprecated&&!(retired&&hasCurrentSuccessor(targetOwner,entry.kind,entry.to))&&findings.push(`${label}: the canonical name ${entry.to} is itself deprecated`):findings.push(`${label}: the canonical name ${entry.to} is not on the public surface`);const record=policyFor(component,compatibilityContext,entry.tag,entry.kind,entry.from);if(record?(checkRecordWindow(label,record),replacementMatches(targetOwner,entry.kind,record,entry.to)||findings.push(`${label}: its deprecation record names a different replacement than ${entry.to}`)):findings.push(`${label}: no deprecation record retires ${entry.from}`),(entry.kind==="attribute"||entry.kind==="property")&&from&&to&&isBooleanMember(from)&&isBooleanMember(to)){const toAttribute=entry.kind==="attribute"?entry.to:to.attribute,preservesPresence=!!toAttribute&&presenceDefaults.has(`${entry.tag}\0${toAttribute}`);entry.polarity==="inverted"?(to.default===!0&&findings.push(`${label}: an inverted rename must target a boolean that defaults to false`),from.default!==!0&&toAttribute&&inversionCompanions.add(`${entry.tag}\0${toAttribute}`),from.default!==!0&&!preservesPresence&&findings.push(`${label}: ${entry.from} defaults to false, so the inverted rename needs a defaults entry inserting ${toAttribute??entry.to} where both are absent`)):from.default===!0&&to.default!==!0&&!preservesPresence&&findings.push(`${label}: ${entry.from} defaults to true and ${entry.to} to false; declare "polarity": "inverted" or preserve the default with a defaults entry`)}else entry.polarity==="inverted"&&from&&to&&findings.push(`${label}: an inverted rename must retire a boolean for a boolean`)}for(const entry of profile.reviews){const label=entryLabel(origin,"reviews",entry),component=ownerFor(components,compatibilityContext,entry.tag);if(!component){findings.push(`${label}: component is not in the inventory`);continue}covered.add(`${entry.tag}\0${entry.kind}\0${entry.name}`),entry.kind!=="component"&&checkLyraOnly(label,entry.tag,entry.kind,entry.name),checkSharedToken(label,entry.kind,entry.name);const record=policyFor(component,compatibilityContext,entry.tag,entry.kind,entry.name);record?checkRecordWindow(label,record):findings.push(`${label}: no deprecation record retires ${JSON.stringify(entry.name)}`)}for(const entry of profile.detailChanges){const label=entryLabel(origin,"detailChanges",entry),component=ownerFor(components,compatibilityContext,entry.tag);component?surfaceEntry(component,"event",entry.event)?checkLyraOnly(label,entry.tag,"event",entry.event):findings.push(`${label}: the event is not dispatched by ${entry.tag}`):findings.push(`${label}: component is not in the inventory`)}for(const entry of profile.retiredEvents??[]){const label=entryLabel(origin,"retiredEvents",entry),component=ownerFor(components,compatibilityContext,entry.tag);if(!component)findings.push(`${label}: component is not in the inventory`);else{surfaceEntry(component,"event",entry.event)&&findings.push(`${label}: retired event is still dispatched by ${entry.tag}`),deprecationRecordFor(component,"event",entry.event)&&findings.push(`${label}: a retired event must not keep a current deprecation record`);const target=surfaceEntry(component,"event",entry.replacement);(!target||target.deprecated)&&findings.push(`${label}: replacement is not a current event on ${entry.tag}`),checkLyraOnly(label,entry.tag,"event",entry.event)}}for(const entry of profile.propertyChanges??[]){const label=entryLabel(origin,"propertyChanges",entry),component=ownerFor(components,compatibilityContext,entry.tag);if(!component)findings.push(`${label}: component is not in the inventory`);else{const historical=historicalMember(compatibilityContext,entry.tag,"property",entry.property),propertyOwner=historical?.state==="retired"?historical.sourceComponent??historicalSourceComponent(compatibilityContext,entry.tag,historical):component;surfaceEntry(propertyOwner,"property",entry.property)?checkLyraOnly(label,entry.tag,"property",entry.property):findings.push(`${label}: the property is not on the public surface`),entry.former&&surfaceEntry(propertyOwner,"property",entry.former)&&findings.push(`${label}: the former property name is still on the public surface`)}}for(const entry of profile.slotContent){const label=entryLabel(origin,"slotContent",entry),component=ownerFor(components,compatibilityContext,entry.tag);if(!component){findings.push(`${label}: component is not in the inventory`);continue}surfaceEntry(component,"slot",entry.slot)||findings.push(`${label}: the slot is not on the public surface`);const record=policyFor(component,compatibilityContext,entry.tag,"slot-content",entry.slot);record&&(covered.add(`${entry.tag}\0slot-content\0${entry.slot}`),Array.isArray(record.permittedContent)&&JSON.stringify(entry.allow??null)!==JSON.stringify(record.permittedContent)&&findings.push(`${label}: its deprecation record permits ${record.permittedContent.join(", ")}; allow must list exactly those elements`))}for(const entry of profile.defaults){const label=entryLabel(origin,"defaults",entry),component=ownerFor(components,compatibilityContext,entry.tag);if(!component){findings.push(`${label}: component is not in the inventory`);continue}const attribute=surfaceEntry(component,"attribute",entry.attribute);if(!attribute){findings.push(`${label}: the attribute is not on the public surface`);continue}inversionCompanions.has(`${entry.tag}\0${entry.attribute}`)||checkLyraOnly(label,entry.tag,"attribute",entry.attribute),entry.value===!0&&!isBooleanMember(attribute)&&findings.push(`${label}: presence insertion requires a boolean attribute`),attribute.hasDefault&&String(attribute.default)===String(entry.value)&&findings.push(`${label}: the value equals the current default, so there is nothing to preserve`)}for(const entry of profile.globals??[]){const label=entryLabel(origin,"globals",entry);if(entry.kind==="part"){const component=ownerFor(components,compatibilityContext,entry.tag);component?surfaceEntry(component,"part",entry.from)||findings.push(`${label}: the part is not on the public surface`):findings.push(`${label}: component is not in the inventory`)}else entry.kind==="css-property"&&[...components.values()].some(component=>(component.surface?.cssProperties??[]).some(property=>entry.prefix?property.name.startsWith(entry.from):property.name===entry.from))&&findings.push(`${label}: a component still exposes the custom property`)}const coveredModules=new Set;for(const entry of profile.moduleReviews){const label=entryLabel(origin,"moduleReviews",entry),key=moduleReviewKey(entry);coveredModules.add(key);const record=moduleRecords.get(key)??historicalExport(compatibilityContext,entry)?.policy;record?(checkRecordWindow(label,record),!record.replacement?.usage&&!record.replacement?.name&&findings.push(`${label}: canonical replacement is missing`)):findings.push(`${label}: no canonical exportDeprecations record`)}if(!requireCoverage)continue;for(const record of[...exportDeprecations,...Object.values(compatibilityContext?.records??{}).filter(entry=>entry.state==="retired"&&entry.key.scope==="export").map(entry=>entry.policy)])removalMajorMatches(record)&&!coveredModules.has(moduleReviewKey(record))&&findings.push(`${origin}: ${record.kind} ${record.module??""} ${record.name} is removed in ${profile.aliasRemovalMajor}.0.0 but has no moduleReviews entry`);const coverageOwners=new Map((inventory?.components??[]).map(component=>[component.tag,deprecationRecords(component)]));for(const entry of Object.values(compatibilityContext?.records??{}))entry.state!=="retired"||entry.key.scope!=="member"||coverageOwners.set(entry.key.tag,[...coverageOwners.get(entry.key.tag)??[],entry.policy]);for(const[tag,records]of coverageOwners){const component={tag};for(const record of records){if(!removalMajorMatches(record))continue;const keys=[[record.kind,record.kind==="component"?component.tag:record.name]];record.kind==="property"&&record.attribute&&keys.push(["attribute",record.attribute]);for(const[kind,name]of keys)kind!=="component"&&mirrorOf(component.tag,kind,name)||covered.has(`${component.tag}\0${kind}\0${name}`)||findings.push(`${origin}: ${component.tag} ${kind} ${JSON.stringify(name)} is removed in ${profile.aliasRemovalMajor}.0.0 but has no ${kind==="slot-content"?"slotContent":"rename or review"} entry`)}}}return[...new Set(findings)]}function projectRenameLedger(ledger,inventory,{exportDeprecations=[],compatibilityContext=null,historicalReleaseMajor=null}={}){const findings=validateRenameLedger(ledger,{inventory,exportDeprecations,compatibilityContext,historicalReleaseMajor});if(findings.length)throw new Error(`Invalid Lyra rename ledger: ${findings.join("; ")}`);const components=componentMap(inventory),moduleRecords=new Map(exportDeprecations.map(record=>[moduleReviewKey(record),record])),projection={schemaVersion:1,profiles:ledger.profiles.map(profile=>{const release=`${profile.toMajor}.0.0`,exposure=Object.fromEntries(EXPOSURE_KINDS.map(kind=>[kind,{}]));for(const[kind,names]of requiredExposure(profile))for(const name of[...names.keys()].sort(compareText))exposure[kind][name]=[...new Set([...exposingTags(inventory,kind,name),...compatibilityContext?.exposure?.[kind]?.[name]??[]])].sort(compareText);const recordFor=(entry,kind,name)=>policyFor(components.get(entry.tag),compatibilityContext,entry.tag,kind,name);return{origin:profile.origin,fromMajor:profile.fromMajor,toMajor:profile.toMajor,aliasRemovalMajor:profile.aliasRemovalMajor,renames:profile.renames.map(entry=>{const target=surfaceEntry(replacementOwner(components,compatibilityContext,entry.tag,entry.kind,entry.from),entry.kind,entry.to),attribute=entry.kind==="attribute"?entry.to:target?.attribute,preservesDefault=entry.polarity==="inverted"&&profile.defaults.some(rule=>rule.tag===entry.tag&&rule.attribute===attribute),since=recordFor(entry,entry.kind,entry.from).since,beforeRelease=since===UNRELEASED_VERSION||compareVersions(since,release)<0;return{...structuredClone(entry),since:preservesDefault&&beforeRelease?release:since,...entry.kind==="attribute"?{reflects:!!target?.reflects}:{}}}),defaults:profile.defaults.map(entry=>({...structuredClone(entry),since:release})),retiredEvents:(profile.retiredEvents??[]).map(entry=>({...structuredClone(entry),since:release})),detailChanges:profile.detailChanges.map(entry=>({...structuredClone(entry),since:release})),...Array.isArray(profile.detailFields)?{detailFields:profile.detailFields.map(entry=>structuredClone(entry))}:{},propertyChanges:(profile.propertyChanges??[]).map(entry=>({...structuredClone(entry),since:release})),...Array.isArray(profile.globals)?{globals:profile.globals.map(entry=>structuredClone(entry))}:{},reviews:profile.reviews.map(entry=>{const record=recordFor(entry,entry.kind,entry.name),retired=historicalMember(compatibilityContext,entry.tag,entry.kind,entry.name);return{...structuredClone(entry),replacement:String(record.replacement?.usage||record.replacement?.name),...retired?.state==="retired"?{removedIn:retired.removedIn}:{},removalNotBefore:record.removalNotBefore,since:record.since}}),slotContent:profile.slotContent.map(entry=>({...structuredClone(entry),since:release})),moduleReviews:profile.moduleReviews.map(entry=>{const retired=historicalExport(compatibilityContext,entry),record=moduleRecords.get(moduleReviewKey(entry))??retired?.policy;return{...structuredClone(entry),replacement:String(record.replacement.usage||record.replacement.name),...retired?.state==="retired"?{removedIn:retired.removedIn}:{},since:record.since,removalNotBefore:record.removalNotBefore}}),exposure}})},shapeFindings=validateRenameLedgerShape(projection,{projected:!0,historicalReleaseMajor});if(shapeFindings.length)throw new Error(`Invalid Lyra rename projection: ${shapeFindings.join("; ")}`);return projection}function indexBy(entries,keyOf){const index=new Map;for(const entry of entries){const key=keyOf(entry),list=index.get(key)??[];list.push(entry),index.set(key,list)}return index}const ownerKey=(tag,kind,name)=>`${tag}\0${kind}\0${name}`,nameKey=(kind,name)=>`${kind}\0${name}`;function createRenameProfiles(projection,{lyraVersion=null}={}){const findings=validateRenameLedgerShape(projection,{projected:!0});if(findings.length)throw new Error(`Invalid Lyra rename projection: ${findings.join("; ")}`);if(lyraVersion!==null&&!parseVersion(lyraVersion))throw new Error(`Invalid Lyra version: ${lyraVersion}`);const profiles=new Map,availableAcrossProfiles=entry=>lyraVersion===null||entry.since!==UNRELEASED_VERSION&&compareVersions(entry.since,lyraVersion)<=0,allRenames=projection.profiles.flatMap(profile=>profile.renames).filter(availableAcrossProfiles),allDefaults=projection.profiles.flatMap(profile=>profile.defaults).filter(availableAcrossProfiles);for(const full of projection.profiles){const skipped=[],available=list=>(full[list]??[]).filter(entry=>{if(lyraVersion===null)return!0;const availableFrom=list==="detailFields"?entry.observedIn:entry.since;return availableFrom!==UNRELEASED_VERSION&&compareVersions(availableFrom,lyraVersion)<=0?!0:(skipped.push({list,...entry}),!1)}).map(entry=>{if(!entry.removedIn||lyraVersion===null||compareVersions(lyraVersion,entry.removedIn)>=0)return entry;const{removedIn,...beforeRemoval}=entry;return beforeRemoval}),data={...full,...Object.fromEntries(PROFILE_LIST_KEYS.map(list=>[list,available(list)]))},renamesByOwner=new Map(data.renames.map(entry=>[ownerKey(entry.tag,entry.kind,entry.from),entry])),renamesByName=indexBy(data.renames,entry=>nameKey(entry.kind,entry.from)),renamesByTarget=indexBy(data.renames,entry=>nameKey(entry.kind,entry.to)),reviewsByOwner=new Map(data.reviews.map(entry=>[ownerKey(entry.tag,entry.kind,entry.name),entry])),reviewsByName=indexBy(data.reviews,entry=>nameKey(entry.kind,entry.name)),detailsByOwner=new Map(data.detailChanges.map(entry=>[ownerKey(entry.tag,"event",entry.event),entry])),detailsByName=indexBy(data.detailChanges,entry=>entry.event),fieldsByOwnerEvent=new Map((data.detailFields??[]).map(entry=>[ownerKey(entry.tag,"event",entry.event),entry])),fieldsByEvent=indexBy(data.detailFields??[],entry=>entry.event),retiredByOwner=new Map(data.retiredEvents.map(entry=>[ownerKey(entry.tag,"event",entry.event),entry])),retiredByName=indexBy(data.retiredEvents,entry=>entry.event),propertiesByOwner=new Map(data.propertyChanges.flatMap(entry=>[entry.property,...entry.former?[entry.former]:[]].map(name=>[ownerKey(entry.tag,"property",name),entry]))),exposure=new Map(EXPOSURE_KINDS.map(kind=>[kind,new Map(Object.entries(data.exposure[kind]).map(([name,tags2])=>[name,new Set(tags2)]))])),defaultsByTag=indexBy(data.defaults,entry=>entry.tag),slotContentByTag=indexBy(data.slotContent,entry=>entry.tag),tags=new Set([...data.renames,...data.reviews,...data.detailChanges,...data.detailFields??[],...data.retiredEvents,...data.propertyChanges,...data.defaults,...data.slotContent].map(entry=>entry.tag)),renamesNamed=(kind,name)=>renamesByName.get(nameKey(kind,name))??[],exposersOf=(kind,name)=>exposure.get(kind)?.get(name)??new Set,movers=(kind,from,to)=>new Set(renamesNamed(kind,from).filter(entry=>entry.to===to&&!entry.polarity).map(entry=>entry.tag)),subset=(left,right)=>[...left].every(tag=>right.has(tag));profiles.set(data.origin,{origin:data.origin,fromMajor:data.fromMajor,toMajor:data.toMajor,aliasRemovalMajor:data.aliasRemovalMajor,data,skipped,tags,isEmpty:tags.size===0&&data.moduleReviews.length===0&&data.globals.length===0,renameFor:(tag,kind,name)=>renamesByOwner.get(ownerKey(tag,kind,name))??null,renamesNamed,renamesOnto:(kind,name)=>renamesByTarget.get(nameKey(kind,name))??[],reviewFor:(tag,kind,name)=>reviewsByOwner.get(ownerKey(tag,kind,name))??null,reviewsNamed:(kind,name)=>reviewsByName.get(nameKey(kind,name))??[],detailFor:(tag,event)=>detailsByOwner.get(ownerKey(tag,"event",event))??null,detailsNamed:event=>detailsByName.get(event)??[],detailFieldFor:(tag,event)=>fieldsByOwnerEvent.get(ownerKey(tag,"event",event))??null,detailFieldsNamed:event=>fieldsByEvent.get(event)??[],retiredEventFor:(tag,event)=>retiredByOwner.get(ownerKey(tag,"event",event))??null,retiredEventsNamed:event=>retiredByName.get(event)??[],propertyChangeFor:(tag,property)=>propertiesByOwner.get(ownerKey(tag,"property",property))??null,defaultsFor:tag=>defaultsByTag.get(tag)??[],preservedDefaultsFor:tag=>allDefaults.filter(entry=>entry.tag===tag),defaultAliasesFor:(tag,attribute)=>{const aliases=new Set([attribute]);for(const rename of allRenames)rename.tag!==tag||rename.kind!=="attribute"||(rename.from===attribute&&aliases.add(rename.to),rename.to===attribute&&aliases.add(rename.from));return[...aliases]},slotContentFor:tag=>slotContentByTag.get(tag)??[],exposes:(kind,name,tag)=>!!tag&&exposersOf(kind,name).has(tag),exposers:(kind,name)=>[...exposersOf(kind,name)],targetKeepers(kind,from,to){const moving=movers(kind,from,to);return[...exposersOf(kind,to)].filter(tag=>!moving.has(tag))},sourceKeepers(kind,from,to){const moving=movers(kind,from,to);return[...exposersOf(kind,from)].filter(tag=>!moving.has(tag))},isGlobal(kind,from){const entries=renamesNamed(kind,from);if(!entries.length||entries.some(entry=>entry.polarity))return!1;const targets=new Set(entries.map(entry=>entry.to));if(targets.size!==1)return!1;const[to]=targets,moving=movers(kind,from,to);return exposersOf(kind,from).size>0&&subset(exposersOf(kind,from),moving)&&subset(exposersOf(kind,to),moving)},gainedOwners(kind,name){const onto=(renamesByTarget.get(nameKey(kind,name))??[]).filter(entry=>!entry.polarity);if(!onto.length)return[];const gained=new Set(onto.map(entry=>entry.tag));return[...exposersOf(kind,name)].filter(tag=>!gained.has(tag)).length?[...gained].sort(compareText):[]}})}return profiles}export{LYRA_RENAME_ORIGINS,compareVersions,createRenameProfiles,emptyRenameLedger,emptyRenameProjection,mirroredMembers,projectRenameLedger,validateRenameLedger,validateRenameLedgerShape};
1
+ const LYRA_RENAME_LEDGER_SCHEMA_VERSION=1,UNRELEASED_VERSION="unreleased",LYRA_RENAME_PROFILES=Object.freeze([Object.freeze({origin:"lyra-v21",fromMajor:21,toMajor:22,aliasRemovalMajor:23}),Object.freeze({origin:"lyra-v22",fromMajor:22,toMajor:23,aliasRemovalMajor:24}),Object.freeze({origin:"lyra-v25",fromMajor:25,toMajor:26,aliasRemovalMajor:28}),Object.freeze({origin:"lyra-v26",fromMajor:26,toMajor:27,aliasRemovalMajor:29})]),LYRA_RENAME_ORIGINS=Object.freeze(LYRA_RENAME_PROFILES.map(profile=>profile.origin)),RENAME_KINDS=Object.freeze(["attribute","property","event","part","css-property","slot"]),REVIEW_KINDS=Object.freeze(["component","attribute","property","event","slot","part","css-property","css-state","method"]),EXPOSURE_KINDS=Object.freeze(["event","part","css-property"]),SURFACE_SECTIONS=Object.freeze({attribute:"attributes",property:"properties",event:"events",part:"parts","css-property":"cssProperties",slot:"slots","css-state":"cssStates",method:"methods"}),TAG_PATTERN=/^lr-[a-z0-9]+(?:-[a-z0-9]+)*$/,ELEMENT_PATTERN=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/,JS_IDENTIFIER_PATTERN=/^[A-Za-z_$][\w$]*$/,VERSION_PATTERN=/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/,NAME_PATTERNS=Object.freeze({component:TAG_PATTERN,attribute:/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/,property:JS_IDENTIFIER_PATTERN,method:JS_IDENTIFIER_PATTERN,event:/^lr-[a-z0-9]+(?:-[a-z0-9]+)*$/,part:/^[A-Za-z0-9_-]+$/,slot:/^(?:[A-Za-z0-9_-]+)?$/,"css-property":/^--[A-Za-z0-9_-]+$/,"css-state":/^[a-z][a-z0-9-]*$/}),MODULE_REVIEW_KINDS=["entry-point","stylesheet","function","type","constant","class","window-event","root-attribute"],MODULE_PATH_PATTERN=/^\.\/[A-Za-z0-9_./-]+\.(?:js|css)$/,moduleReviewKey=entry=>[entry.kind,entry.module??"",entry.name].join("\0"),PROFILE_LIST_KEYS=["renames","defaults","detailChanges","detailFields","propertyChanges","retiredEvents","reviews","slotContent","moduleReviews","globals"],OPTIONAL_PROFILE_LISTS=["propertyChanges","retiredEvents","detailFields","globals"],AUTHORED_PROFILE_KEYS=["origin","fromMajor","toMajor","aliasRemovalMajor",...PROFILE_LIST_KEYS,"rules"],GLOBAL_KINDS=Object.freeze(["module","export","locale-key","css-property","part"]),PROFILE_RULES=Object.freeze(["theme-scopes"]),PACKAGE_SPECIFIER_PATTERN=/^@aceshooting\/lyra-[a-z]+(?:\/[A-Za-z0-9_./-]*)?$/,PROJECTED_PROFILE_KEYS=[...AUTHORED_PROFILE_KEYS,"exposure"];function isPlainObject(value){return value!==null&&typeof value=="object"&&!Array.isArray(value)}function unknownKeys(value,allowed){return Object.keys(value).filter(key=>!allowed.includes(key))}function compareText(left,right){return left<right?-1:left>right?1:0}function parseVersion(version){const match=VERSION_PATTERN.exec(String(version??""));return match?match.slice(1,4).map(Number):null}function compareVersions(left,right){const a=parseVersion(left),b=parseVersion(right);if(!a||!b)return null;for(let index=0;index<3;index+=1)if(a[index]!==b[index])return a[index]-b[index];return 0}function majorOf(version){return parseVersion(version)?.[0]??null}function emptyRenameLedger(){return{schemaVersion:1,profiles:LYRA_RENAME_PROFILES.map(header=>({...header,...Object.fromEntries(PROFILE_LIST_KEYS.filter(list=>!["detailFields","globals"].includes(list)).map(list=>[list,[]]))}))}}function emptyRenameProjection(){return{schemaVersion:1,profiles:emptyRenameLedger().profiles.map(profile=>({...profile,exposure:Object.fromEntries(EXPOSURE_KINDS.map(kind=>[kind,{}]))}))}}function entryLabel(origin,list,entry){return list==="globals"?`${origin}: global ${entry?.kind} ${entry?.from}`:list==="moduleReviews"?`${origin}: module review ${entry?.kind} ${entry?.module??""} ${entry?.name}`:list==="renames"?`${origin}: rename ${entry?.tag} ${entry?.kind} ${entry?.from}`:list==="defaults"?`${origin}: default ${entry?.tag} ${entry?.attribute}`:list==="retiredEvents"?`${origin}: retired event ${entry?.tag} ${entry?.event}`:list==="detailChanges"?`${origin}: detail change ${entry?.tag} ${entry?.event}`:list==="detailFields"?`${origin}: detail field ${entry?.tag} ${entry?.event} ${entry?.detailType}.${entry?.field}`:list==="propertyChanges"?`${origin}: property change ${entry?.tag} ${entry?.property}`:list==="slotContent"?`${origin}: slot content ${entry?.tag} ${JSON.stringify(entry?.slot??null)}`:`${origin}: review ${entry?.tag} ${entry?.kind} ${JSON.stringify(entry?.name??null)}`}function sortKey(list,entry){return list==="moduleReviews"?moduleReviewKey(entry):list==="globals"?[entry.kind,entry.from,entry.tag??""].join("\0"):list==="renames"?[entry.tag,entry.kind,entry.from].join("\0"):list==="defaults"?[entry.tag,entry.attribute].join("\0"):list==="detailChanges"||list==="retiredEvents"?[entry.tag,entry.event].join("\0"):list==="detailFields"?[entry.tag,entry.event,entry.module,entry.detailType,entry.field].join("\0"):list==="propertyChanges"?[entry.tag,entry.property].join("\0"):list==="slotContent"?[entry.tag,entry.slot].join("\0"):[entry.tag,entry.kind,entry.name].join("\0")}function validateName(findings,label,kind,field,value){return typeof value!="string"||!NAME_PATTERNS[kind]?.test(value)?(findings.push(`${label}: ${field} must be a valid ${kind} name, got ${JSON.stringify(value)}`),!1):!0}function validateProjectedSince(findings,label,entry,projected){projected&&entry.since!==UNRELEASED_VERSION&&!parseVersion(entry.since)&&findings.push(`${label}: since must be a version or ${JSON.stringify(UNRELEASED_VERSION)}`)}function validateRemovedIn(findings,label,entry){Object.hasOwn(entry,"removedIn")&&(!parseVersion(entry.removedIn)||compareVersions(entry.removedIn,entry.removalNotBefore)<0)&&findings.push(`${label}: removedIn must be a version at or after removalNotBefore`)}function validateModuleReviewEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,["kind","module","name",...projected?["replacement","since","removalNotBefore","removedIn"]:[]]);if(unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),MODULE_REVIEW_KINDS.includes(entry.kind)||findings.push(`${label}: unsupported module review kind ${entry.kind}`),entry.kind==="entry-point"||entry.kind==="stylesheet")Object.hasOwn(entry,"module")&&findings.push(`${label}: path reviews use name, not module`),typeof entry.name!="string"||!MODULE_PATH_PATTERN.test(entry.name)||entry.name.includes("..")?findings.push(`${label}: name must be a package subpath`):entry.name.endsWith(".css")!==(entry.kind==="stylesheet")&&findings.push(`${label}: stylesheet reviews must name CSS; entry-point reviews must name JavaScript`);else{entry.module!=="."&&(typeof entry.module!="string"||!MODULE_PATH_PATTERN.test(entry.module)||!entry.module.endsWith(".js")||entry.module.includes(".."))&&findings.push(`${label}: module must be a JavaScript package subpath`);const pattern=entry.kind==="window-event"?NAME_PATTERNS.event:entry.kind==="root-attribute"?/^data-lr-[a-z0-9-]+$/:JS_IDENTIFIER_PATTERN;(typeof entry.name!="string"||!pattern.test(entry.name))&&findings.push(`${label}: invalid module review name`)}projected&&((typeof entry.replacement!="string"||!entry.replacement.trim())&&findings.push(`${label}: replacement text missing`),parseVersion(entry.removalNotBefore)||findings.push(`${label}: removalNotBefore must be a version`),validateRemovedIn(findings,label,entry)),validateProjectedSince(findings,label,entry,projected)}function validateGlobalEntry(findings,label,entry){const unknown=unknownKeys(entry,["kind","from","to","module","prefix","except","tag","summary","since"]);if(unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),!GLOBAL_KINDS.includes(entry.kind)){findings.push(`${label}: kind must be one of ${GLOBAL_KINDS.join(", ")}`);return}(typeof entry.summary!="string"||!entry.summary.trim())&&findings.push(`${label}: summary text missing`),parseVersion(entry.since)||findings.push(`${label}: since must be a version`);const optional=entry.kind==="locale-key";if(entry.kind==="module"){const valid=value=>typeof value=="string"&&PACKAGE_SPECIFIER_PATTERN.test(value)&&!value.includes("..");if(valid(entry.from)||findings.push(`${label}: from must be an @aceshooting package specifier`),valid(entry.to)||findings.push(`${label}: to must be an @aceshooting package specifier`),valid(entry.from)&&entry.from===entry.to&&findings.push(`${label}: from and to must differ`),Object.hasOwn(entry,"module")&&findings.push(`${label}: module entries do not take a module`),Object.hasOwn(entry,"prefix")&&entry.prefix!==!0&&findings.push(`${label}: prefix may only be true`),entry.prefix===!0&&!(entry.from?.endsWith("/")&&entry.to?.endsWith("/"))&&findings.push(`${label}: a prefix entry's from and to must end with a slash`),Object.hasOwn(entry,"except")){const exceptions=entry.except;entry.prefix!==!0||!Array.isArray(exceptions)||!exceptions.length||!exceptions.every(value=>typeof value=="string"&&value.startsWith(entry.from)&&value.endsWith("/"))?findings.push(`${label}: except needs prefix and a list of slash-terminated prefixes below from`):JSON.stringify(exceptions)!==JSON.stringify([...exceptions].sort(compareText))&&findings.push(`${label}: except must be sorted`)}return}if(entry.kind==="css-property"||entry.kind==="part"){(typeof entry.to!="string"||!entry.to.trim())&&findings.push(`${label}: to must name the replacement`),(Object.hasOwn(entry,"module")||Object.hasOwn(entry,"except"))&&findings.push(`${label}: ${entry.kind} entries take no module or except`),entry.kind==="part"?((typeof entry.tag!="string"||!TAG_PATTERN.test(entry.tag))&&findings.push(`${label}: tag must be an lr-* element name`),Object.hasOwn(entry,"prefix")&&findings.push(`${label}: prefix applies to custom properties only`),validateName(findings,label,"part","from",entry.from)):(Object.hasOwn(entry,"tag")&&findings.push(`${label}: custom properties are not tag-owned`),validateName(findings,label,"css-property","from",entry.from),Object.hasOwn(entry,"prefix")&&(entry.prefix!==!0||!entry.from.endsWith("-"))&&findings.push(`${label}: a prefix needs prefix: true and a name ending in a hyphen`));return}for(const field of["prefix","except","tag"])Object.hasOwn(entry,field)&&findings.push(`${label}: ${field} applies to module, part and custom property entries only`);(typeof entry.from!="string"||!JS_IDENTIFIER_PATTERN.test(entry.from))&&findings.push(`${label}: from must be an identifier`),(typeof entry.to!="string"||!JS_IDENTIFIER_PATTERN.test(entry.to))&&findings.push(`${label}: to must be an identifier`),entry.from===entry.to&&findings.push(`${label}: from and to must differ`),entry.kind==="export"&&entry.module!=="."&&findings.push(`${label}: an export entry needs module "." (the package root)`),optional&&Object.hasOwn(entry,"module")&&findings.push(`${label}: locale keys do not take a module`)}function validateRulesList(findings,origin,rules){if(!Array.isArray(rules)){findings.push(`${origin}: rules must be an array`);return}for(const rule of rules)PROFILE_RULES.includes(rule)||findings.push(`${origin}: unknown rule ${JSON.stringify(rule)}`);JSON.stringify(rules)!==JSON.stringify([...new Set(rules)].sort(compareText))&&findings.push(`${origin}: rules must be sorted and unique`)}function validateRenameEntry(findings,label,entry,projected){const allowed=["tag","kind","from","to","polarity",...projected?["since","reflects"]:[]],unknown=unknownKeys(entry,allowed);if(unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),!RENAME_KINDS.includes(entry.kind)){findings.push(`${label}: kind must be one of ${RENAME_KINDS.join(", ")}`);return}const validFrom=validateName(findings,label,entry.kind,"from",entry.from),validTo=validateName(findings,label,entry.kind,"to",entry.to);validFrom&&validTo&&entry.from===entry.to&&findings.push(`${label}: from and to must differ`),entry.kind==="slot"&&(entry.from===""||entry.to==="")&&findings.push(`${label}: the default slot can be reviewed but not renamed`),Object.hasOwn(entry,"polarity")&&(entry.polarity!=="inverted"&&findings.push(`${label}: polarity may only be "inverted"`),entry.kind!=="attribute"&&entry.kind!=="property"&&findings.push(`${label}: only attribute and property renames can invert polarity`)),validateProjectedSince(findings,label,entry,projected),projected&&Object.hasOwn(entry,"reflects")!==(entry.kind==="attribute")&&findings.push(`${label}: reflects is recorded for attribute renames only`),projected&&entry.kind==="attribute"&&typeof entry.reflects!="boolean"&&findings.push(`${label}: reflects must be a boolean`)}function validateDefaultEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,["tag","attribute","value",...projected?["since"]:[]]);unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),validateName(findings,label,"attribute","attribute",entry.attribute),Object.hasOwn(entry,"value")?typeof entry.value!="string"&&entry.value!==!0&&!(typeof entry.value=="number"&&Number.isFinite(entry.value))&&findings.push(`${label}: value must be a string, a finite number, or true (presence)`):findings.push(`${label}: needs a value`),validateProjectedSince(findings,label,entry,projected)}function validateSummary(findings,label,summary){(typeof summary!="string"||summary.trim().length<16||/\n/.test(summary))&&findings.push(`${label}: summary must be a single-line description of the change`)}function validateDetailEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,["tag","event","summary",...projected?["since"]:[]]);unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),validateName(findings,label,"event","event",entry.event),validateSummary(findings,label,entry.summary),validateProjectedSince(findings,label,entry,projected)}function validateDetailFieldEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,["tag","event","module","detailType","field","relation","replacement","role","notice","removalNotBefore","observedIn","removedIn"]);if(unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),validateName(findings,label,"event","event",entry.event),(typeof entry.module!="string"||!/^(?:layout|conversation)\/[a-z0-9-]+\/[a-z0-9-]+(?:\.class|-shared)\.ts$/u.test(entry.module))&&findings.push(`${label}: module must be a canonical layout or conversation detail source path`),(typeof entry.detailType!="string"||!JS_IDENTIFIER_PATTERN.test(entry.detailType))&&findings.push(`${label}: detailType must be an exported type name`),(typeof entry.field!="string"||!JS_IDENTIFIER_PATTERN.test(entry.field))&&findings.push(`${label}: field must be a property name`),["equal","inverse"].includes(entry.relation)||findings.push(`${label}: relation must be equal or inverse`),entry.replacement!=="expanded"&&findings.push(`${label}: replacement must be expanded`),(!["request","settled"].includes(entry.role)||typeof entry.event!="string"||entry.role==="request"!==entry.event.endsWith("-request"))&&findings.push(`${label}: role must match whether the event is a request event`),(typeof entry.notice!="string"||!/^\/\*\*[\s\S]*@deprecated\b[\s\S]*\*\/$/u.test(entry.notice))&&findings.push(`${label}: notice must preserve the exact deprecation JSDoc`),entry.removalNotBefore!=="23.0.0"&&findings.push(`${label}: removalNotBefore must preserve the declared 23.0.0 floor`),entry.observedIn!=="22.0.0"&&findings.push(`${label}: observedIn must name the earliest verified packed observation, 22.0.0`),projected&&typeof entry.observedIn=="string"&&!parseVersion(entry.observedIn)&&findings.push(`${label}: observedIn must be a semantic version`),entry.removedIn!==void 0&&entry.removedIn!=="24.0.0"&&findings.push(`${label}: removedIn requires the separately authorized actual 24.0.0 retirement`),entry.removedIn!==void 0){const removal=parseVersion(entry.removedIn),floor=parseVersion(entry.removalNotBefore),observed=parseVersion(entry.observedIn);(!removal||!floor||!observed||removal[0]<floor[0]+1||removal[0]<observed[0]+2)&&findings.push(`${label}: removedIn must clear the declared floor and a full intervening published major`)}}function validateRetiredEventEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,["tag","event","replacement","summary",...projected?["since"]:[]]);unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),validateName(findings,label,"event","event",entry.event),validateName(findings,label,"event","replacement",entry.replacement),entry.event===entry.replacement&&findings.push(`${label}: replacement must differ from the retired event`),validateSummary(findings,label,entry.summary),validateProjectedSince(findings,label,entry,projected)}function validatePropertyChangeEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,["tag","property","former","summary",...projected?["since"]:[]]);unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),validateName(findings,label,"property","property",entry.property),Object.hasOwn(entry,"former")&&validateName(findings,label,"property","former",entry.former)&&entry.former===entry.property&&findings.push(`${label}: former must differ from property`),validateSummary(findings,label,entry.summary),validateProjectedSince(findings,label,entry,projected)}function validateSlotContentEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,["tag","slot","report","allow","summary",...projected?["since"]:[]]);unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),validateName(findings,label,"slot","slot",entry.slot);const lists=["report","allow"].filter(key=>Object.hasOwn(entry,key));if(lists.length!==1)findings.push(`${label}: needs exactly one of report (elements to report) or allow (the only elements kept)`);else{const elements=entry[lists[0]];(!Array.isArray(elements)||!elements.length||!elements.every(element=>typeof element=="string"&&ELEMENT_PATTERN.test(element))||JSON.stringify(elements)!==JSON.stringify([...new Set(elements)].sort(compareText)))&&findings.push(`${label}: ${lists[0]} must be a sorted, unique list of lowercase element names`)}validateSummary(findings,label,entry.summary),validateProjectedSince(findings,label,entry,projected)}function validateReviewEntry(findings,label,entry,projected){const unknown=unknownKeys(entry,projected?["tag","kind","name","replacement","removalNotBefore","since","removedIn"]:["tag","kind","name"]);if(unknown.length&&findings.push(`${label}: unknown key(s) ${unknown.join(", ")}`),!REVIEW_KINDS.includes(entry.kind)){findings.push(`${label}: kind must be one of ${REVIEW_KINDS.join(", ")}`);return}validateName(findings,label,entry.kind,"name",entry.name)&&entry.kind==="component"&&entry.name!==entry.tag&&findings.push(`${label}: a component review must name its own tag`),projected&&((typeof entry.replacement!="string"||!entry.replacement)&&findings.push(`${label}: replacement text missing`),majorOf(entry.removalNotBefore)===null&&findings.push(`${label}: removalNotBefore must be a version`),validateRemovedIn(findings,label,entry)),validateProjectedSince(findings,label,entry,projected)}function requiredExposure(profile){const required=new Map(EXPOSURE_KINDS.map(kind=>[kind,new Map])),need=(kind,name,tag)=>{if(!required.has(kind))return;const tags=required.get(kind).get(name)??new Set;tag&&tags.add(tag),required.get(kind).set(name,tags)};for(const entry of profile.renames??[])need(entry.kind,entry.from,entry.tag),need(entry.kind,entry.to,entry.tag);for(const entry of profile.reviews??[])need(entry.kind,entry.name,entry.tag);for(const entry of profile.detailChanges??[])need("event",entry.event,entry.tag);for(const entry of profile.retiredEvents??[])need("event",entry.event,null),need("event",entry.replacement,entry.tag);for(const entry of profile.propertyChanges??[])need("property",entry.property,entry.tag);return required}function validateExposure(findings,profile){const origin=profile.origin;if(!isPlainObject(profile.exposure)){findings.push(`${origin}: exposure must be an object`);return}const unknown=unknownKeys(profile.exposure,EXPOSURE_KINDS);unknown.length&&findings.push(`${origin}: exposure has unknown kind(s) ${unknown.join(", ")}`);const required=requiredExposure(profile);for(const kind of EXPOSURE_KINDS){const recorded=profile.exposure[kind];if(!isPlainObject(recorded)){findings.push(`${origin}: exposure.${kind} must be an object`);continue}for(const[name,tags]of Object.entries(recorded))required.get(kind).has(name)||findings.push(`${origin}: exposure.${kind} records unreferenced name ${name}`),(!Array.isArray(tags)||!tags.every(tag=>typeof tag=="string"&&TAG_PATTERN.test(tag))||JSON.stringify(tags)!==JSON.stringify([...new Set(tags)].sort(compareText)))&&findings.push(`${origin}: exposure.${kind}.${name} must be a sorted, unique tag list`);for(const[name,tags]of required.get(kind)){if(!Object.hasOwn(recorded,name)){findings.push(`${origin}: exposure.${kind}.${name} is missing`);continue}const recordedTags=new Set(recorded[name]??[]);for(const tag of tags)recordedTags.has(tag)||findings.push(`${origin}: exposure.${kind}.${name} must include ${tag}`)}}}function validateRenameLedgerShape(ledger,{projected=!1,historicalReleaseMajor=null}={}){const findings=[];if(!isPlainObject(ledger))return["rename ledger must be an object"];const unknownTop=unknownKeys(ledger,projected?["schemaVersion","profiles"]:["$comment","schemaVersion","profiles"]);if(unknownTop.length&&findings.push(`rename ledger has unknown key(s) ${unknownTop.join(", ")}`),ledger.schemaVersion!==1&&findings.push("rename ledger schemaVersion must be 1"),!Array.isArray(ledger.profiles))return[...findings,"rename ledger profiles must be an array"];const origins=ledger.profiles.map(profile=>profile?.origin),historicalCount=!Number.isInteger(historicalReleaseMajor)||historicalReleaseMajor<22?0:historicalReleaseMajor<26?2:historicalReleaseMajor<27?3:0,expectedOrigins=historicalCount&&JSON.stringify(origins)===JSON.stringify(LYRA_RENAME_ORIGINS.slice(0,historicalCount))?LYRA_RENAME_ORIGINS.slice(0,historicalCount):LYRA_RENAME_ORIGINS;JSON.stringify(origins)!==JSON.stringify(expectedOrigins)&&findings.push(`rename ledger profiles must be exactly ${expectedOrigins.join(", ")} in that order`);for(const profile of ledger.profiles){if(!isPlainObject(profile)){findings.push("rename ledger profiles must be objects");continue}const origin=String(profile.origin),unknown=unknownKeys(profile,projected?PROJECTED_PROFILE_KEYS:AUTHORED_PROFILE_KEYS);unknown.length&&findings.push(`${origin}: unknown key(s) ${unknown.join(", ")}`);const header=LYRA_RENAME_PROFILES.find(candidate=>candidate.origin===profile.origin);if(header)for(const field of["fromMajor","toMajor","aliasRemovalMajor"])profile[field]!==header[field]&&findings.push(`${origin}: ${field} must be ${header[field]}`);for(const list of PROFILE_LIST_KEYS){if(OPTIONAL_PROFILE_LISTS.includes(list)&&profile[list]===void 0)continue;if(!Array.isArray(profile[list])){findings.push(`${origin}: ${list} must be an array`);continue}const seen=new Set,keys=[];for(const entry of profile[list]){const label=entryLabel(origin,list,entry);if(!isPlainObject(entry)){findings.push(`${origin}: ${list} entries must be objects`);continue}list!=="moduleReviews"&&list!=="globals"&&(typeof entry.tag!="string"||!TAG_PATTERN.test(entry.tag))&&findings.push(`${label}: tag must be an lr-* element name`),list==="globals"?validateGlobalEntry(findings,label,entry):list==="moduleReviews"?(validateModuleReviewEntry(findings,label,entry,projected),projected&&majorOf(entry.removalNotBefore)!==profile.aliasRemovalMajor&&findings.push(`${label}: removalNotBefore must match the profile's aliasRemovalMajor`)):list==="renames"?validateRenameEntry(findings,label,entry,projected):list==="defaults"?validateDefaultEntry(findings,label,entry,projected):list==="detailChanges"?validateDetailEntry(findings,label,entry,projected):list==="detailFields"?validateDetailFieldEntry(findings,label,entry,projected):list==="retiredEvents"?(validateRetiredEventEntry(findings,label,entry,projected),projected&&entry.since!==`${profile.toMajor}.0.0`&&findings.push(`${label}: since must match the target major release`)):list==="propertyChanges"?validatePropertyChangeEntry(findings,label,entry,projected):list==="slotContent"?validateSlotContentEntry(findings,label,entry,projected):validateReviewEntry(findings,label,entry,projected);const key=sortKey(list,entry);seen.has(key)&&findings.push(`${label}: duplicate entry`),seen.add(key),keys.push(key)}JSON.stringify(keys)!==JSON.stringify([...keys].sort(compareText))&&findings.push(`${origin}: ${list} must be sorted by ${list==="moduleReviews"?"kind, module and name":list==="globals"?"kind and name":"tag and name"}`)}if(profile.rules!==void 0&&validateRulesList(findings,origin,profile.rules),!Array.isArray(profile.renames)||!Array.isArray(profile.reviews))continue;const sources=new Map;for(const entry of profile.renames){const names=sources.get(entry.kind)??new Set;names.add(entry.from),sources.set(entry.kind,names)}for(const entry of profile.renames)sources.get(entry.kind)?.has(entry.to)&&findings.push(`${entryLabel(origin,"renames",entry)}: target ${entry.to} is itself renamed; rename directly to the final name`);const renamed=new Set(profile.renames.map(entry=>`${entry.tag}\0${entry.kind}\0${entry.from}`));for(const entry of profile.reviews)renamed.has(`${entry.tag}\0${entry.kind}\0${entry.name}`)&&findings.push(`${entryLabel(origin,"reviews",entry)}: a member is either renamed or reviewed, not both`);projected&&validateExposure(findings,profile)}if(ledger.profiles.length>=2){const historicalFields=ledger.profiles[0]?.detailFields,nextFields=ledger.profiles[1]?.detailFields;(Array.isArray(historicalFields)||Array.isArray(nextFields))&&JSON.stringify(historicalFields??[])!==JSON.stringify(nextFields??[])&&findings.push("lyra-v21 and lyra-v22 detailFields must preserve the same verified field exposures")}return findings}function componentMap(inventory){return new Map((inventory?.components??[]).map(component=>[component.tag,component]))}function surfaceEntry(component,kind,name){return(component?.surface?.[SURFACE_SECTIONS[kind]]??[]).find(entry=>entry.name===name)??null}function hasCurrentSuccessor(component,kind,name){const seen=new Set;for(;;){const key=`${kind}\0${name}`;if(seen.has(key))return!1;seen.add(key);const member=surfaceEntry(component,kind,name);if(!member)return!1;if(!member.deprecated)return!0;const replacement=deprecationRecordFor(component,kind,name)?.replacement;if(!replacement||replacement.kind==="component"||replacement.kind==="host-css-property")return!1;kind=replacement.kind,name=replacement.name}}function deprecationRecords(component){const records=[...component?.maturity?.deprecations??[]],componentRecord=component?.maturity?.deprecated;return isPlainObject(componentRecord)&&!records.some(record=>record.kind==="component")&&records.push(componentRecord),records}function deprecationRecordFor(component,kind,name){const records=deprecationRecords(component);if(kind==="component")return records.find(record=>record.kind==="component")??null;const direct=records.find(record=>record.kind===kind&&record.name===name);return direct||kind!=="attribute"?direct??null:records.find(record=>record.kind==="property"&&record.attribute===name)??null}function historicalMember(context,tag,kind,name){const key=JSON.stringify(["member",tag,kind,name]);return context?.records?.[key]??context?.records?.[context?.aliases?.[key]]??null}function historicalSourceComponent(context,tag,record=null){const historical=record??historicalMember(context,tag,"component",tag);return historical?.state!=="retired"?null:historical.sourceComponent??context?.sourceComponents?.[tag]??null}function historicalExport(context,entry){return context?.records?.[JSON.stringify(["export",entry.kind,entry.module??null,entry.name])]??null}function ownerFor(components,context,tag){const current=components.get(tag);return current||historicalSourceComponent(context,tag)}function policyFor(component,context,tag,kind,name){return deprecationRecordFor(component,kind,name)??historicalMember(context,tag,kind,name)?.policy??null}function replacementOwner(components,context,tag,kind,name){const historical=historicalMember(context,tag,kind,name);return components.get(historical?.state==="retired"?historical.replacementOwner:tag)}function replacementMatches(component,kind,record,to){const replacement=record?.replacement;return isPlainObject(replacement)?replacement.kind===kind&&replacement.name===to?!0:kind==="attribute"&&replacement.kind==="property"?surfaceEntry(component,"property",replacement.name)?.attribute===to:!1:!1}function isBooleanMember(entry){return String(entry?.type??"").split("|").map(part=>part.trim()).includes("boolean")}function exposingTags(inventory,kind,name){return(inventory?.components??[]).filter(component=>surfaceEntry(component,kind,name)).map(component=>component.tag).sort(compareText)}const UNMIRRORED_CLASSIFICATIONS=new Set(["conceptual-only","unsupported"]);function mirroredMembers(inventory){const upstreamComponents=new Map;for(const ecosystem of Object.values(inventory?.upstreams??{}))for(const component of ecosystem?.components??[])upstreamComponents.set(component.tag,component);const mirrored=new Map;for(const mapping of inventory?.mappings??[]){if(!mapping?.targetTag||UNMIRRORED_CLASSIFICATIONS.has(mapping.classification))continue;const upstream=upstreamComponents.get(mapping.upstreamTag);for(const[kind,section]of Object.entries(SURFACE_SECTIONS)){const rewrites=mapping.rewrites?.[section]??[],renamedAway=new Set(rewrites.map(rule=>rule?.from)),names=[...rewrites.map(rule=>rule?.to).filter(name=>typeof name=="string"),...(upstream?.surface?.[section]??[]).map(entry=>entry.name).filter(name=>!renamedAway.has(name))];for(const name of names){const key=`${mapping.targetTag}\0${kind}\0${name}`;mirrored.has(key)||mirrored.set(key,mapping.upstreamTag)}}}return mirrored}function validateRenameLedger(ledger,{inventory,exportDeprecations=[],requireCoverage=!1,sharedTokens=null,compatibilityContext=null,historicalReleaseMajor=null}){const findings=validateRenameLedgerShape(ledger,{historicalReleaseMajor});if(findings.length)return findings;const components=componentMap(inventory);if(!Array.isArray(exportDeprecations))return["exportDeprecations must be an array"];if(!exportDeprecations.every(isPlainObject))return["exportDeprecations entries must be objects"];const moduleRecords=new Map(exportDeprecations.map(record=>[moduleReviewKey(record),record]));moduleRecords.size!==exportDeprecations.length&&findings.push("exportDeprecations contains duplicate module records");const mirrored=mirroredMembers(inventory),mirrorOf=(tag,kind,name)=>mirrored.get(`${tag}\0${kind}\0${name}`)??null;for(const profile of ledger.profiles){const origin=profile.origin,covered=new Set,release=`${profile.toMajor}.0.0`,removalMajorMatches=record=>majorOf(record?.removalNotBefore)===profile.aliasRemovalMajor,checkRecordWindow=(label,record)=>{if(removalMajorMatches(record)||findings.push(`${label}: its deprecation record must set removalNotBefore to ${profile.aliasRemovalMajor}.0.0`),record?.since===UNRELEASED_VERSION)return;const since=compareVersions(record?.since,release);(since===null||since>0)&&findings.push(`${label}: its deprecation record must start no later than ${release} (since ${JSON.stringify(record?.since)})`)},checkLyraOnly=(label,tag,kind,name)=>{const upstream=mirrorOf(tag,kind,name);upstream&&findings.push(`${label}: ${name} mirrors ${upstream}; mirrored names never change`)},checkSharedToken=(label,kind,name)=>{kind==="css-property"&&sharedTokens?.has(name)&&findings.push(`${label}: ${name} is a canonical design token, not a component-owned custom property`)},presenceDefaults=new Set(ledger.profiles.filter(candidate=>candidate.toMajor<=profile.toMajor).flatMap(candidate=>candidate.defaults).filter(entry=>entry.value===!0).map(entry=>`${entry.tag}\0${entry.attribute}`)),inversionCompanions=new Set;for(const entry of profile.detailFields??[]){const label=entryLabel(origin,"detailFields",entry),component=ownerFor(components,compatibilityContext,entry.tag);component?surfaceEntry(component,"event",entry.event)||findings.push(`${label}: the event is not dispatched by ${entry.tag}`):findings.push(`${label}: component is not in the inventory`)}for(const entry of profile.renames){const label=entryLabel(origin,"renames",entry),component=ownerFor(components,compatibilityContext,entry.tag);if(!component){findings.push(`${label}: component is not in the inventory`);continue}covered.add(`${entry.tag}\0${entry.kind}\0${entry.from}`),checkLyraOnly(label,entry.tag,entry.kind,entry.from),checkSharedToken(label,entry.kind,entry.from),checkSharedToken(label,entry.kind,entry.to);const historical=historicalMember(compatibilityContext,entry.tag,entry.kind,entry.from),retired=historical?.state==="retired",from=surfaceEntry(retired?historicalSourceComponent(compatibilityContext,entry.tag,historical):component,entry.kind,entry.from),targetOwner=replacementOwner(components,compatibilityContext,entry.tag,entry.kind,entry.from),to=surfaceEntry(targetOwner,entry.kind,entry.to);from?from.deprecated||findings.push(`${label}: the alias ${entry.from} is not marked deprecated in the manifest`):findings.push(`${label}: the deprecated alias ${entry.from} is not on the public surface`),to?to.deprecated&&!(retired&&hasCurrentSuccessor(targetOwner,entry.kind,entry.to))&&findings.push(`${label}: the canonical name ${entry.to} is itself deprecated`):findings.push(`${label}: the canonical name ${entry.to} is not on the public surface`);const record=policyFor(component,compatibilityContext,entry.tag,entry.kind,entry.from);if(record?(checkRecordWindow(label,record),replacementMatches(targetOwner,entry.kind,record,entry.to)||findings.push(`${label}: its deprecation record names a different replacement than ${entry.to}`)):findings.push(`${label}: no deprecation record retires ${entry.from}`),(entry.kind==="attribute"||entry.kind==="property")&&from&&to&&isBooleanMember(from)&&isBooleanMember(to)){const toAttribute=entry.kind==="attribute"?entry.to:to.attribute,preservesPresence=!!toAttribute&&presenceDefaults.has(`${entry.tag}\0${toAttribute}`);entry.polarity==="inverted"?(to.default===!0&&findings.push(`${label}: an inverted rename must target a boolean that defaults to false`),from.default!==!0&&toAttribute&&inversionCompanions.add(`${entry.tag}\0${toAttribute}`),from.default!==!0&&!preservesPresence&&findings.push(`${label}: ${entry.from} defaults to false, so the inverted rename needs a defaults entry inserting ${toAttribute??entry.to} where both are absent`)):from.default===!0&&to.default!==!0&&!preservesPresence&&findings.push(`${label}: ${entry.from} defaults to true and ${entry.to} to false; declare "polarity": "inverted" or preserve the default with a defaults entry`)}else entry.polarity==="inverted"&&from&&to&&findings.push(`${label}: an inverted rename must retire a boolean for a boolean`)}for(const entry of profile.reviews){const label=entryLabel(origin,"reviews",entry),component=ownerFor(components,compatibilityContext,entry.tag);if(!component){findings.push(`${label}: component is not in the inventory`);continue}covered.add(`${entry.tag}\0${entry.kind}\0${entry.name}`),entry.kind!=="component"&&checkLyraOnly(label,entry.tag,entry.kind,entry.name),checkSharedToken(label,entry.kind,entry.name);const record=policyFor(component,compatibilityContext,entry.tag,entry.kind,entry.name);record?checkRecordWindow(label,record):findings.push(`${label}: no deprecation record retires ${JSON.stringify(entry.name)}`)}for(const entry of profile.detailChanges){const label=entryLabel(origin,"detailChanges",entry),component=ownerFor(components,compatibilityContext,entry.tag);component?surfaceEntry(component,"event",entry.event)?checkLyraOnly(label,entry.tag,"event",entry.event):findings.push(`${label}: the event is not dispatched by ${entry.tag}`):findings.push(`${label}: component is not in the inventory`)}for(const entry of profile.retiredEvents??[]){const label=entryLabel(origin,"retiredEvents",entry),component=ownerFor(components,compatibilityContext,entry.tag);if(!component)findings.push(`${label}: component is not in the inventory`);else{surfaceEntry(component,"event",entry.event)&&findings.push(`${label}: retired event is still dispatched by ${entry.tag}`),deprecationRecordFor(component,"event",entry.event)&&findings.push(`${label}: a retired event must not keep a current deprecation record`);const target=surfaceEntry(component,"event",entry.replacement);(!target||target.deprecated)&&findings.push(`${label}: replacement is not a current event on ${entry.tag}`),checkLyraOnly(label,entry.tag,"event",entry.event)}}for(const entry of profile.propertyChanges??[]){const label=entryLabel(origin,"propertyChanges",entry),component=ownerFor(components,compatibilityContext,entry.tag);if(!component)findings.push(`${label}: component is not in the inventory`);else{const historical=historicalMember(compatibilityContext,entry.tag,"property",entry.property),propertyOwner=historical?.state==="retired"?historical.sourceComponent??historicalSourceComponent(compatibilityContext,entry.tag,historical):component;surfaceEntry(propertyOwner,"property",entry.property)?checkLyraOnly(label,entry.tag,"property",entry.property):findings.push(`${label}: the property is not on the public surface`),entry.former&&surfaceEntry(propertyOwner,"property",entry.former)&&findings.push(`${label}: the former property name is still on the public surface`)}}for(const entry of profile.slotContent){const label=entryLabel(origin,"slotContent",entry),component=ownerFor(components,compatibilityContext,entry.tag);if(!component){findings.push(`${label}: component is not in the inventory`);continue}surfaceEntry(component,"slot",entry.slot)||findings.push(`${label}: the slot is not on the public surface`);const record=policyFor(component,compatibilityContext,entry.tag,"slot-content",entry.slot);record&&(covered.add(`${entry.tag}\0slot-content\0${entry.slot}`),Array.isArray(record.permittedContent)&&JSON.stringify(entry.allow??null)!==JSON.stringify(record.permittedContent)&&findings.push(`${label}: its deprecation record permits ${record.permittedContent.join(", ")}; allow must list exactly those elements`))}for(const entry of profile.defaults){const label=entryLabel(origin,"defaults",entry),component=ownerFor(components,compatibilityContext,entry.tag);if(!component){findings.push(`${label}: component is not in the inventory`);continue}const attribute=surfaceEntry(component,"attribute",entry.attribute);if(!attribute){findings.push(`${label}: the attribute is not on the public surface`);continue}inversionCompanions.has(`${entry.tag}\0${entry.attribute}`)||checkLyraOnly(label,entry.tag,"attribute",entry.attribute),entry.value===!0&&!isBooleanMember(attribute)&&findings.push(`${label}: presence insertion requires a boolean attribute`),attribute.hasDefault&&String(attribute.default)===String(entry.value)&&findings.push(`${label}: the value equals the current default, so there is nothing to preserve`)}for(const entry of profile.globals??[]){const label=entryLabel(origin,"globals",entry);if(entry.kind==="part"){const component=ownerFor(components,compatibilityContext,entry.tag);component?surfaceEntry(component,"part",entry.from)||findings.push(`${label}: the part is not on the public surface`):findings.push(`${label}: component is not in the inventory`)}else entry.kind==="css-property"&&[...components.values()].some(component=>(component.surface?.cssProperties??[]).some(property=>entry.prefix?property.name.startsWith(entry.from):property.name===entry.from))&&findings.push(`${label}: a component still exposes the custom property`)}const coveredModules=new Set;for(const entry of profile.moduleReviews){const label=entryLabel(origin,"moduleReviews",entry),key=moduleReviewKey(entry);coveredModules.add(key);const record=moduleRecords.get(key)??historicalExport(compatibilityContext,entry)?.policy;record?(checkRecordWindow(label,record),!record.replacement?.usage&&!record.replacement?.name&&findings.push(`${label}: canonical replacement is missing`)):findings.push(`${label}: no canonical exportDeprecations record`)}if(!requireCoverage)continue;for(const record of[...exportDeprecations,...Object.values(compatibilityContext?.records??{}).filter(entry=>entry.state==="retired"&&entry.key.scope==="export").map(entry=>entry.policy)])removalMajorMatches(record)&&!coveredModules.has(moduleReviewKey(record))&&findings.push(`${origin}: ${record.kind} ${record.module??""} ${record.name} is removed in ${profile.aliasRemovalMajor}.0.0 but has no moduleReviews entry`);const coverageOwners=new Map((inventory?.components??[]).map(component=>[component.tag,deprecationRecords(component)]));for(const entry of Object.values(compatibilityContext?.records??{}))entry.state!=="retired"||entry.key.scope!=="member"||coverageOwners.set(entry.key.tag,[...coverageOwners.get(entry.key.tag)??[],entry.policy]);for(const[tag,records]of coverageOwners){const component={tag};for(const record of records){if(!removalMajorMatches(record))continue;const keys=[[record.kind,record.kind==="component"?component.tag:record.name]];record.kind==="property"&&record.attribute&&keys.push(["attribute",record.attribute]);for(const[kind,name]of keys)kind!=="component"&&mirrorOf(component.tag,kind,name)||covered.has(`${component.tag}\0${kind}\0${name}`)||findings.push(`${origin}: ${component.tag} ${kind} ${JSON.stringify(name)} is removed in ${profile.aliasRemovalMajor}.0.0 but has no ${kind==="slot-content"?"slotContent":"rename or review"} entry`)}}}return[...new Set(findings)]}function projectRenameLedger(ledger,inventory,{exportDeprecations=[],compatibilityContext=null,historicalReleaseMajor=null}={}){const findings=validateRenameLedger(ledger,{inventory,exportDeprecations,compatibilityContext,historicalReleaseMajor});if(findings.length)throw new Error(`Invalid Lyra rename ledger: ${findings.join("; ")}`);const components=componentMap(inventory),moduleRecords=new Map(exportDeprecations.map(record=>[moduleReviewKey(record),record])),projection={schemaVersion:1,profiles:ledger.profiles.map(profile=>{const release=`${profile.toMajor}.0.0`,exposure=Object.fromEntries(EXPOSURE_KINDS.map(kind=>[kind,{}]));for(const[kind,names]of requiredExposure(profile))for(const name of[...names.keys()].sort(compareText))exposure[kind][name]=[...new Set([...exposingTags(inventory,kind,name),...compatibilityContext?.exposure?.[kind]?.[name]??[]])].sort(compareText);const recordFor=(entry,kind,name)=>policyFor(components.get(entry.tag),compatibilityContext,entry.tag,kind,name);return{origin:profile.origin,fromMajor:profile.fromMajor,toMajor:profile.toMajor,aliasRemovalMajor:profile.aliasRemovalMajor,renames:profile.renames.map(entry=>{const target=surfaceEntry(replacementOwner(components,compatibilityContext,entry.tag,entry.kind,entry.from),entry.kind,entry.to),attribute=entry.kind==="attribute"?entry.to:target?.attribute,preservesDefault=entry.polarity==="inverted"&&profile.defaults.some(rule=>rule.tag===entry.tag&&rule.attribute===attribute),since=recordFor(entry,entry.kind,entry.from).since,beforeRelease=since===UNRELEASED_VERSION||compareVersions(since,release)<0;return{...structuredClone(entry),since:preservesDefault&&beforeRelease?release:since,...entry.kind==="attribute"?{reflects:!!target?.reflects}:{}}}),defaults:profile.defaults.map(entry=>({...structuredClone(entry),since:release})),retiredEvents:(profile.retiredEvents??[]).map(entry=>({...structuredClone(entry),since:release})),detailChanges:profile.detailChanges.map(entry=>({...structuredClone(entry),since:release})),...Array.isArray(profile.detailFields)?{detailFields:profile.detailFields.map(entry=>structuredClone(entry))}:{},propertyChanges:(profile.propertyChanges??[]).map(entry=>({...structuredClone(entry),since:release})),...Array.isArray(profile.globals)?{globals:profile.globals.map(entry=>structuredClone(entry))}:{},...Array.isArray(profile.rules)?{rules:[...profile.rules]}:{},reviews:profile.reviews.map(entry=>{const record=recordFor(entry,entry.kind,entry.name),retired=historicalMember(compatibilityContext,entry.tag,entry.kind,entry.name);return{...structuredClone(entry),replacement:String(record.replacement?.usage||record.replacement?.name),...retired?.state==="retired"?{removedIn:retired.removedIn}:{},removalNotBefore:record.removalNotBefore,since:record.since}}),slotContent:profile.slotContent.map(entry=>({...structuredClone(entry),since:release})),moduleReviews:profile.moduleReviews.map(entry=>{const retired=historicalExport(compatibilityContext,entry),record=moduleRecords.get(moduleReviewKey(entry))??retired?.policy;return{...structuredClone(entry),replacement:String(record.replacement.usage||record.replacement.name),...retired?.state==="retired"?{removedIn:retired.removedIn}:{},since:record.since,removalNotBefore:record.removalNotBefore}}),exposure}})},shapeFindings=validateRenameLedgerShape(projection,{projected:!0,historicalReleaseMajor});if(shapeFindings.length)throw new Error(`Invalid Lyra rename projection: ${shapeFindings.join("; ")}`);return projection}function indexBy(entries,keyOf){const index=new Map;for(const entry of entries){const key=keyOf(entry),list=index.get(key)??[];list.push(entry),index.set(key,list)}return index}const ownerKey=(tag,kind,name)=>`${tag}\0${kind}\0${name}`,nameKey=(kind,name)=>`${kind}\0${name}`;function createRenameProfiles(projection,{lyraVersion=null}={}){const findings=validateRenameLedgerShape(projection,{projected:!0});if(findings.length)throw new Error(`Invalid Lyra rename projection: ${findings.join("; ")}`);if(lyraVersion!==null&&!parseVersion(lyraVersion))throw new Error(`Invalid Lyra version: ${lyraVersion}`);const profiles=new Map,availableAcrossProfiles=entry=>lyraVersion===null||entry.since!==UNRELEASED_VERSION&&compareVersions(entry.since,lyraVersion)<=0,allRenames=projection.profiles.flatMap(profile=>profile.renames).filter(availableAcrossProfiles),allDefaults=projection.profiles.flatMap(profile=>profile.defaults).filter(availableAcrossProfiles);for(const full of projection.profiles){const skipped=[],available=list=>(full[list]??[]).filter(entry=>{if(lyraVersion===null)return!0;const availableFrom=list==="detailFields"?entry.observedIn:entry.since;return availableFrom!==UNRELEASED_VERSION&&compareVersions(availableFrom,lyraVersion)<=0?!0:(skipped.push({list,...entry}),!1)}).map(entry=>{if(!entry.removedIn||lyraVersion===null||compareVersions(lyraVersion,entry.removedIn)>=0)return entry;const{removedIn,...beforeRemoval}=entry;return beforeRemoval}),rulesApply=lyraVersion===null||compareVersions(lyraVersion,`${full.toMajor}.0.0`)>=0;rulesApply||skipped.push(...(full.rules??[]).map(rule=>({list:"rules",rule})));const data={...full,...Object.fromEntries(PROFILE_LIST_KEYS.map(list=>[list,available(list)])),rules:rulesApply?[...full.rules??[]]:[]},renamesByOwner=new Map(data.renames.map(entry=>[ownerKey(entry.tag,entry.kind,entry.from),entry])),renamesByName=indexBy(data.renames,entry=>nameKey(entry.kind,entry.from)),renamesByTarget=indexBy(data.renames,entry=>nameKey(entry.kind,entry.to)),reviewsByOwner=new Map(data.reviews.map(entry=>[ownerKey(entry.tag,entry.kind,entry.name),entry])),reviewsByName=indexBy(data.reviews,entry=>nameKey(entry.kind,entry.name)),detailsByOwner=new Map(data.detailChanges.map(entry=>[ownerKey(entry.tag,"event",entry.event),entry])),detailsByName=indexBy(data.detailChanges,entry=>entry.event),fieldsByOwnerEvent=new Map((data.detailFields??[]).map(entry=>[ownerKey(entry.tag,"event",entry.event),entry])),fieldsByEvent=indexBy(data.detailFields??[],entry=>entry.event),retiredByOwner=new Map(data.retiredEvents.map(entry=>[ownerKey(entry.tag,"event",entry.event),entry])),retiredByName=indexBy(data.retiredEvents,entry=>entry.event),propertiesByOwner=new Map(data.propertyChanges.flatMap(entry=>[entry.property,...entry.former?[entry.former]:[]].map(name=>[ownerKey(entry.tag,"property",name),entry]))),exposure=new Map(EXPOSURE_KINDS.map(kind=>[kind,new Map(Object.entries(data.exposure[kind]).map(([name,tags2])=>[name,new Set(tags2)]))])),defaultsByTag=indexBy(data.defaults,entry=>entry.tag),slotContentByTag=indexBy(data.slotContent,entry=>entry.tag),tags=new Set([...data.renames,...data.reviews,...data.detailChanges,...data.detailFields??[],...data.retiredEvents,...data.propertyChanges,...data.defaults,...data.slotContent].map(entry=>entry.tag)),renamesNamed=(kind,name)=>renamesByName.get(nameKey(kind,name))??[],exposersOf=(kind,name)=>exposure.get(kind)?.get(name)??new Set,movers=(kind,from,to)=>new Set(renamesNamed(kind,from).filter(entry=>entry.to===to&&!entry.polarity).map(entry=>entry.tag)),subset=(left,right)=>[...left].every(tag=>right.has(tag));profiles.set(data.origin,{origin:data.origin,fromMajor:data.fromMajor,toMajor:data.toMajor,aliasRemovalMajor:data.aliasRemovalMajor,data,skipped,tags,isEmpty:tags.size===0&&data.moduleReviews.length===0&&data.globals.length===0&&data.rules.length===0,renameFor:(tag,kind,name)=>renamesByOwner.get(ownerKey(tag,kind,name))??null,renamesNamed,renamesOnto:(kind,name)=>renamesByTarget.get(nameKey(kind,name))??[],reviewFor:(tag,kind,name)=>reviewsByOwner.get(ownerKey(tag,kind,name))??null,reviewsNamed:(kind,name)=>reviewsByName.get(nameKey(kind,name))??[],detailFor:(tag,event)=>detailsByOwner.get(ownerKey(tag,"event",event))??null,detailsNamed:event=>detailsByName.get(event)??[],detailFieldFor:(tag,event)=>fieldsByOwnerEvent.get(ownerKey(tag,"event",event))??null,detailFieldsNamed:event=>fieldsByEvent.get(event)??[],retiredEventFor:(tag,event)=>retiredByOwner.get(ownerKey(tag,"event",event))??null,retiredEventsNamed:event=>retiredByName.get(event)??[],propertyChangeFor:(tag,property)=>propertiesByOwner.get(ownerKey(tag,"property",property))??null,defaultsFor:tag=>defaultsByTag.get(tag)??[],preservedDefaultsFor:tag=>allDefaults.filter(entry=>entry.tag===tag),defaultAliasesFor:(tag,attribute)=>{const aliases=new Set([attribute]);for(const rename of allRenames)rename.tag!==tag||rename.kind!=="attribute"||(rename.from===attribute&&aliases.add(rename.to),rename.to===attribute&&aliases.add(rename.from));return[...aliases]},slotContentFor:tag=>slotContentByTag.get(tag)??[],exposes:(kind,name,tag)=>!!tag&&exposersOf(kind,name).has(tag),exposers:(kind,name)=>[...exposersOf(kind,name)],targetKeepers(kind,from,to){const moving=movers(kind,from,to);return[...exposersOf(kind,to)].filter(tag=>!moving.has(tag))},sourceKeepers(kind,from,to){const moving=movers(kind,from,to);return[...exposersOf(kind,from)].filter(tag=>!moving.has(tag))},isGlobal(kind,from){const entries=renamesNamed(kind,from);if(!entries.length||entries.some(entry=>entry.polarity))return!1;const targets=new Set(entries.map(entry=>entry.to));if(targets.size!==1)return!1;const[to]=targets,moving=movers(kind,from,to);return exposersOf(kind,from).size>0&&subset(exposersOf(kind,from),moving)&&subset(exposersOf(kind,to),moving)},gainedOwners(kind,name){const onto=(renamesByTarget.get(nameKey(kind,name))??[]).filter(entry=>!entry.polarity);if(!onto.length)return[];const gained=new Set(onto.map(entry=>entry.tag));return[...exposersOf(kind,name)].filter(tag=>!gained.has(tag)).length?[...gained].sort(compareText):[]}})}return profiles}export{LYRA_RENAME_ORIGINS,compareVersions,createRenameProfiles,emptyRenameLedger,emptyRenameProjection,mirroredMembers,projectRenameLedger,validateRenameLedger,validateRenameLedgerShape};