@kerfjs/ui 5.0.0-beta.110 → 5.0.0-beta.112

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 (211) hide show
  1. package/README.md +3 -0
  2. package/ai/component-catalog.json +161 -8
  3. package/ai/component-composition.json +119 -2
  4. package/ai/components/README.md +2 -0
  5. package/ai/components/backdrop-image.md +1 -0
  6. package/ai/components/button.md +5 -2
  7. package/ai/components/content-item.md +1 -0
  8. package/ai/components/editor-frame.md +80 -0
  9. package/ai/components/summary-row.md +5 -2
  10. package/ai/components/surface-scaffold.md +3 -0
  11. package/ai/components/value-preview.md +75 -0
  12. package/ai/public-api-signatures-v1.md +207 -50
  13. package/ai/skill.md +17 -0
  14. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  15. package/dist/backdrop-image.d.ts +11 -0
  16. package/dist/backdrop-image.js +1 -1
  17. package/dist/browser/content-item.js +1 -0
  18. package/dist/browser/editor-frame.js +3 -0
  19. package/dist/browser/value-preview.js +3 -0
  20. package/dist/button.d.ts +4 -2
  21. package/dist/button.js +1 -1
  22. package/dist/catalog-resources.d.ts +2 -1
  23. package/dist/catalog.d.ts +2 -1
  24. package/dist/catalog.js +6 -4
  25. package/dist/catalog.js.map +1 -1
  26. package/dist/{chunk-JCCN3U6X.js → chunk-2J7XKAZX.js} +3 -2
  27. package/dist/chunk-2J7XKAZX.js.map +1 -0
  28. package/dist/chunk-3KFMLOTH.js +201 -0
  29. package/dist/chunk-3KFMLOTH.js.map +1 -0
  30. package/dist/{chunk-ATGAJVZ6.js → chunk-3OP66VXU.js} +64 -4
  31. package/dist/chunk-3OP66VXU.js.map +1 -0
  32. package/dist/{chunk-ZNROKDSZ.js → chunk-3V4OCDTL.js} +2 -8
  33. package/dist/chunk-3V4OCDTL.js.map +1 -0
  34. package/dist/{chunk-3GLYIIFR.js → chunk-3WWX2O4G.js} +3 -2
  35. package/dist/chunk-3WWX2O4G.js.map +1 -0
  36. package/dist/{chunk-SCDWURBO.js → chunk-5CLMV5YU.js} +21 -3
  37. package/dist/chunk-5CLMV5YU.js.map +1 -0
  38. package/dist/{chunk-P7EJ4CAD.js → chunk-ETFP7HAR.js} +2 -2
  39. package/dist/{chunk-P7EJ4CAD.js.map → chunk-ETFP7HAR.js.map} +1 -1
  40. package/dist/chunk-F653O57X.js +41 -0
  41. package/dist/chunk-F653O57X.js.map +1 -0
  42. package/dist/{chunk-SOFPUW3O.js → chunk-IIQ2XIMU.js} +4 -3
  43. package/dist/chunk-IIQ2XIMU.js.map +1 -0
  44. package/dist/chunk-IRC5N7FA.js +16 -0
  45. package/dist/chunk-IRC5N7FA.js.map +1 -0
  46. package/dist/{chunk-SQ5DH66U.js → chunk-ITENVZ6Q.js} +2 -15
  47. package/dist/chunk-ITENVZ6Q.js.map +1 -0
  48. package/dist/{chunk-4UB5Z3ZZ.js → chunk-K67XBCHG.js} +8 -4
  49. package/dist/chunk-K67XBCHG.js.map +1 -0
  50. package/dist/chunk-OK7FUK7U.js +9 -0
  51. package/dist/chunk-OK7FUK7U.js.map +1 -0
  52. package/dist/{chunk-WSEJ2FDE.js → chunk-OUCHU7JB.js} +2 -2
  53. package/dist/{chunk-WSEJ2FDE.js.map → chunk-OUCHU7JB.js.map} +1 -1
  54. package/dist/{chunk-KPINJHIC.js → chunk-PS24PVR6.js} +3 -1
  55. package/dist/chunk-PS24PVR6.js.map +1 -0
  56. package/dist/{chunk-6CDILXF4.js → chunk-S3DCRI2M.js} +31 -5
  57. package/dist/chunk-S3DCRI2M.js.map +1 -0
  58. package/dist/chunk-V6N3YLTM.js +56 -0
  59. package/dist/chunk-V6N3YLTM.js.map +1 -0
  60. package/dist/{chunk-HQZG3OFL.js → chunk-XRZSK2WC.js} +3 -2
  61. package/dist/chunk-XRZSK2WC.js.map +1 -0
  62. package/dist/{chunk-CPSGUGAY.js → chunk-YPAQFBMN.js} +4 -4
  63. package/dist/{chunk-CPSGUGAY.js.map → chunk-YPAQFBMN.js.map} +1 -1
  64. package/dist/collapsible-panel.d.ts +1 -0
  65. package/dist/collapsible-panel.js +3 -2
  66. package/dist/collapsible-panel.js.map +1 -1
  67. package/dist/content-item.d.ts +46 -2
  68. package/dist/content-item.js +4 -1
  69. package/dist/editor-frame.d.ts +41 -0
  70. package/dist/editor-frame.js +3 -0
  71. package/dist/editor-frame.js.map +1 -0
  72. package/dist/index.d.ts +5 -2
  73. package/dist/index.js +27 -23
  74. package/dist/list-action-row.js +2 -1
  75. package/dist/list-detail-dialog.d.ts +2 -1
  76. package/dist/list-detail-dialog.js +5 -4
  77. package/dist/list-detail-dialog.js.map +1 -1
  78. package/dist/list-item.js +2 -1
  79. package/dist/{nav-stack-CU_0dKtI.d.ts → nav-stack-CrZF95VO.d.ts} +2 -1
  80. package/dist/nav-stack.d.ts +2 -1
  81. package/dist/nav-stack.js +2 -1
  82. package/dist/pane.d.ts +3 -23
  83. package/dist/pane.js +2 -1
  84. package/dist/split-view.d.ts +2 -1
  85. package/dist/split-view.js +2 -1
  86. package/dist/styles/backdrop-image.css +24 -1
  87. package/dist/styles/components/actions/popup-menu/popup-menu.css +1 -1
  88. package/dist/styles/components/collections/summary-row/summary-row.css +7 -3
  89. package/dist/styles/components/data-display/value-preview/value-preview.css +18 -0
  90. package/dist/styles/components/surfaces/backdrop-image/backdrop-image.css +24 -1
  91. package/dist/styles/components/surfaces/content-item/content-item.css +52 -0
  92. package/dist/styles/components/surfaces/editor-frame/editor-frame.css +121 -0
  93. package/dist/styles/components/surfaces/surface-scaffold/surface-scaffold.css +25 -1
  94. package/dist/styles/content-item.css +3 -168
  95. package/dist/styles/editor-frame.css +121 -0
  96. package/dist/styles/foundation.css +9 -4
  97. package/dist/styles/popup-menu.css +1 -1
  98. package/dist/styles/styles.css +2 -0
  99. package/dist/styles/summary-row.css +7 -3
  100. package/dist/styles/surface-scaffold.css +25 -1
  101. package/dist/styles/value-preview.css +18 -0
  102. package/dist/styles/webawesome.css +2 -2
  103. package/dist/summary-row.d.ts +20 -11
  104. package/dist/summary-row.js +2 -1
  105. package/dist/surface-scaffold-register.js +1 -1
  106. package/dist/surface-scaffold.d.ts +30 -2
  107. package/dist/surface-scaffold.js +2 -2
  108. package/dist/surface-tint-DQgYCaP9.d.ts +25 -0
  109. package/dist/tab-navigator.d.ts +2 -1
  110. package/dist/tab-navigator.js +2 -1
  111. package/dist/tab-navigator.js.map +1 -1
  112. package/dist/toolbar.d.ts +1 -0
  113. package/dist/value-preview.d.ts +21 -0
  114. package/dist/value-preview.js +3 -0
  115. package/dist/value-preview.js.map +1 -0
  116. package/dist/wire-catalog.d.ts +2 -1
  117. package/dist/wire-catalog.js +1 -1
  118. package/dist/wire-content-items.js +88 -0
  119. package/dist/wire-content-items.js.map +1 -1
  120. package/dist/workbench-dialog.d.ts +2 -1
  121. package/dist/workbench-dialog.js +4 -3
  122. package/dist/workbench-dialog.js.map +1 -1
  123. package/dist/workbench.d.ts +2 -1
  124. package/dist/workbench.js +2 -1
  125. package/docs/accessibility.md +6 -0
  126. package/docs/backdrop-image.md +4 -0
  127. package/docs/button.md +15 -0
  128. package/docs/component-contract.md +34 -4
  129. package/docs/component-selection.md +16 -1
  130. package/docs/design/templates/button/emphasis-dark.svg +1 -1
  131. package/docs/design/templates/button-dark.svg +1 -1
  132. package/docs/design/templates/checkbox/error-dark.svg +1 -1
  133. package/docs/design/templates/checkbox/states.svg +1 -1
  134. package/docs/design/templates/checkbox-dark.svg +1 -1
  135. package/docs/design/templates/checkbox-group/horizontal-dark.svg +1 -1
  136. package/docs/design/templates/checkbox-group/vertical.svg +1 -1
  137. package/docs/design/templates/checkbox-group-dark.svg +1 -1
  138. package/docs/design/templates/checkbox-group.svg +1 -1
  139. package/docs/design/templates/checkbox.svg +1 -1
  140. package/docs/design/templates/choice-chips/color-palette-dark.svg +1 -1
  141. package/docs/design/templates/choice-chips-dark.svg +1 -1
  142. package/docs/design/templates/color-field/error-dark.svg +1 -1
  143. package/docs/design/templates/color-field/hint.svg +1 -1
  144. package/docs/design/templates/color-field-dark.svg +1 -1
  145. package/docs/design/templates/color-field.svg +1 -1
  146. package/docs/design/templates/color-picker/gray.svg +1 -1
  147. package/docs/design/templates/color-picker/hsb.svg +1 -1
  148. package/docs/design/templates/color-picker/rgb.svg +1 -1
  149. package/docs/design/templates/color-picker.svg +1 -1
  150. package/docs/design/templates/color-swatches/palette-dark.svg +1 -1
  151. package/docs/design/templates/color-swatches-dark.svg +1 -1
  152. package/docs/design/templates/disclosure/embedded.svg +1 -1
  153. package/docs/design/templates/disclosure/framed-sunken.svg +1 -1
  154. package/docs/design/templates/disclosure/plain.svg +1 -1
  155. package/docs/design/templates/disclosure.svg +1 -1
  156. package/docs/design/templates/message-composer/chat.svg +1 -1
  157. package/docs/design/templates/message-composer.svg +1 -1
  158. package/docs/design/templates/progress-bar/determinate.svg +1 -1
  159. package/docs/design/templates/progress-bar/tones.svg +1 -1
  160. package/docs/design/templates/progress-bar.svg +1 -1
  161. package/docs/design/templates/radio-group/horizontal-dark.svg +1 -1
  162. package/docs/design/templates/radio-group/vertical.svg +1 -1
  163. package/docs/design/templates/radio-group-dark.svg +1 -1
  164. package/docs/design/templates/radio-group.svg +1 -1
  165. package/docs/design/templates/search-field/empty.svg +1 -1
  166. package/docs/design/templates/search-field/tokens.svg +1 -1
  167. package/docs/design/templates/search-field.svg +1 -1
  168. package/docs/design/templates/select/form.svg +1 -1
  169. package/docs/design/templates/select.svg +1 -1
  170. package/docs/design/templates/slider/compact.svg +1 -1
  171. package/docs/design/templates/slider/readout.svg +1 -1
  172. package/docs/design/templates/slider/ticks.svg +1 -1
  173. package/docs/design/templates/slider.svg +1 -1
  174. package/docs/design/templates/switch/states.svg +1 -1
  175. package/docs/design/templates/switch.svg +1 -1
  176. package/docs/design/templates/tag-field/error-dark.svg +1 -1
  177. package/docs/design/templates/tag-field/error.svg +1 -1
  178. package/docs/design/templates/tag-field/tags.svg +1 -1
  179. package/docs/design/templates/tag-field-dark.svg +1 -1
  180. package/docs/design/templates/tag-field.svg +1 -1
  181. package/docs/design/templates/text-field/error-dark.svg +1 -1
  182. package/docs/design/templates/text-field/labeled.svg +1 -1
  183. package/docs/design/templates/text-field/multiline.svg +1 -1
  184. package/docs/design/templates/text-field/search.svg +1 -1
  185. package/docs/design/templates/text-field-dark.svg +1 -1
  186. package/docs/design/templates/text-field.svg +1 -1
  187. package/docs/design/templates/toolbar-control-group/icon-tile-danger-dark.svg +1 -1
  188. package/docs/design/templates/toolbar-control-group-dark.svg +1 -1
  189. package/docs/design/templates.md +3 -1
  190. package/docs/editor-frame.md +47 -0
  191. package/docs/investigations/content-item-media-scroll-performance.md +26 -0
  192. package/docs/layout.md +34 -0
  193. package/docs/manual-test-plan.md +27 -1
  194. package/docs/recipes.md +9 -0
  195. package/docs/surface-scaffold.md +50 -1
  196. package/docs/value-preview.md +16 -0
  197. package/llms.txt +10 -1
  198. package/package.json +13 -1
  199. package/dist/chunk-3GLYIIFR.js.map +0 -1
  200. package/dist/chunk-4UB5Z3ZZ.js.map +0 -1
  201. package/dist/chunk-5USKCWGL.js +0 -113
  202. package/dist/chunk-5USKCWGL.js.map +0 -1
  203. package/dist/chunk-6CDILXF4.js.map +0 -1
  204. package/dist/chunk-ATGAJVZ6.js.map +0 -1
  205. package/dist/chunk-HQZG3OFL.js.map +0 -1
  206. package/dist/chunk-JCCN3U6X.js.map +0 -1
  207. package/dist/chunk-KPINJHIC.js.map +0 -1
  208. package/dist/chunk-SCDWURBO.js.map +0 -1
  209. package/dist/chunk-SOFPUW3O.js.map +0 -1
  210. package/dist/chunk-SQ5DH66U.js.map +0 -1
  211. package/dist/chunk-ZNROKDSZ.js.map +0 -1
package/README.md CHANGED
@@ -273,6 +273,8 @@ interchangeable. `ListItem` and `ListActionRow` deliberately expose no raw
273
273
  | `LoadingSpinner` | `@kerfjs/ui/loading-spinner` | `@kerfjs/ui/loading-spinner.css` |
274
274
  | `Skeleton` | `@kerfjs/ui/skeleton` | `@kerfjs/ui/skeleton.css` |
275
275
  | `SunkenPanel` | `@kerfjs/ui/sunken-panel` | `@kerfjs/ui/sunken-panel.css` |
276
+ | `ValuePreview` | `@kerfjs/ui/value-preview` | `@kerfjs/ui/value-preview.css` |
277
+ | `EditorFrame` | `@kerfjs/ui/editor-frame` | `@kerfjs/ui/editor-frame.css` |
276
278
  | `BackdropImage` | `@kerfjs/ui/backdrop-image` | `@kerfjs/ui/backdrop-image.css` |
277
279
  | `Select` | `@kerfjs/ui/select` | `@kerfjs/ui/select.css` |
278
280
  | `SegmentedControl` | `@kerfjs/ui/segmented-control` | `@kerfjs/ui/segmented-control.css` |
@@ -908,6 +910,7 @@ The detailed rationale lives in the theme contract.
908
910
  - [Apple HIG translation](./docs/apple-hig.md)
909
911
  - [Writing interface copy](./docs/writing.md)
910
912
  - [Accessibility and keyboard contracts](./docs/accessibility.md)
913
+ - [Button current-step semantics](./docs/button.md)
911
914
  - [Component and integration contract](./docs/component-contract.md)
912
915
  - [Spacing and application layout](./docs/layout.md)
913
916
  - [Web Awesome theme contract](./docs/webawesome-theme.md)
@@ -1726,7 +1726,8 @@
1726
1726
  "progressive blur from a once-filtered copy or an app-supplied blurred asset, with an optional dim",
1727
1727
  "fade into a color that also paints below",
1728
1728
  "bottom or full scrim under overlay content",
1729
- "image running beneath clear or overlay chrome (underChrome)"
1729
+ "image running beneath clear or overlay chrome (underChrome)",
1730
+ "opt-in deferOffscreen hero paint deferral for dense image feeds without changing reserved height"
1730
1731
  ],
1731
1732
  "accessibility": [
1732
1733
  "alt describes a meaningful image; pass an empty alt for a decorative one. A tiled image with alt text is exposed as role=\"img\". Parallax and the progressive blur run only with prefers-reduced-motion: no-preference and scroll-driven animation support, and are off under data-kui-effects=\"static\" and on slow-update displays, leaving a static, sharp image; the blurred copy is decorative and aria-hidden. Reduced transparency or increased contrast replaces the fade with a hard edge on solid fadeTo and puts overlay content on a solid fadeTo band. Overlay text needs a scrim or fade color that keeps it at AA."
@@ -2151,6 +2152,7 @@
2151
2152
  "A modal task needs a dialog: render a DialogSurface with a label (or labelledBy plus a heading), its body as children, and its buttons as footer, then open it with openDialog() and close it with closeDialog(), the close button, or any element with data-dialog-close. A raw Web Awesome dropdown that needs a list-compatible menu inset uses PopupSurface.",
2152
2153
  "A dialog body should compose rows, sections, or aligned bare prose through List and ListInsetText.",
2153
2154
  "A modal needs a typed preferred width, viewport gutter, or dynamic viewport-bounded height cap while retaining native body scrolling and footer actions.",
2155
+ "A dialog needs a less prominent built-in close control: closeEmphasis=\"quiet\" softens the glyph while keeping the native button target and close behavior.",
2154
2156
  "A dialog that must leave the page interactive and close on Escape or an outside press uses PopoverDialogSurface; one that fills its body with a SplitView or Pane uses height and bodyLayout=\"fill\".",
2155
2157
  "A chat-style dialog floats its composer over the transcript with footerPresentation=\"floating\" and keeps a heading or notice above one scrolling transcript with bodyLayout=\"fill-last\".",
2156
2158
  "A phone-first task (compose, quick edit, a picker) should rise from the bottom edge as a sheet the person can resize: presentation=\"bottom-sheet\" with height, and resizable with minHeight / maxHeight bounds.",
@@ -2190,12 +2192,14 @@
2190
2192
  "core kerfjs/overlay confirm/prompt/form/choice as a bottom sheet styled by @kerfjs/ui/overlay-sheet.css",
2191
2193
  "resizable core kerfjs/overlay bottom sheet: the kui-overlay-sheet-resizable class plus wireOverlaySheets adds the resizable DialogSurface sheet grabber",
2192
2194
  "header with label text or heading content, headerActions, a close button, and an optional footer; withoutHeader for content that owns its heading and close control",
2195
+ "normal (default) or quiet closeEmphasis on the built-in close button",
2193
2196
  "accessible description by describedBy: the heading ToolbarText descriptionId or app-owned body summary copy",
2194
2197
  "light dismiss (a backdrop press closes; otherwise the panel pulses)"
2195
2198
  ],
2196
2199
  "accessibility": [
2197
2200
  "DialogSurface is a native <dialog> opened modally (showModal): it is in the top layer, the rest of the page is inert, focus stays inside, and it returns to the opener on close. Focus starts on an autofocus element, otherwise on the panel.",
2198
2201
  "Name it with label (also its title text) or labelledBy (the id of a heading, such as a ToolbarText). The close button is named Close (closeLabel) and Escape asks to close; the cancelable kui-request-close event reports source close-button, escape, or backdrop.",
2202
+ "closeEmphasis=\"quiet\" uses a smaller, quiet-colored close glyph inside the same 40px target; hover, focus ring, forced colors, accessible naming, and close requests remain available.",
2199
2203
  "Name a PopoverDialogSurface with label or labelledBy. Describe either surface with describedBy (rendered as aria-describedby on the dialog root), usually the heading ToolbarText descriptionId or app-owned summary copy in the body; rootAttributes accepts only data-* attributes, so it never sets aria-describedby.",
2200
2204
  "A floating header's title uses the normal text color, which keeps WCAG AA over the 80% raised chrome material on any content; the material turns opaque under prefers-reduced-transparency, prefers-contrast: more, forced colors, or without backdrop-filter.",
2201
2205
  "A resizable bottom sheet's grabber is a focusable horizontal separator (\"Resize sheet\") with aria-valuemin, aria-valuenow, and aria-valuemax in visible px; the arrow keys, Page Up/Down, Home, and End resize it, and its slide motion is skipped under prefers-reduced-motion.",
@@ -3046,6 +3050,147 @@
3046
3050
  "recipe": "docs/component-selection.md#ambiguous-choices"
3047
3051
  }
3048
3052
  },
3053
+ {
3054
+ "id": "editor-frame",
3055
+ "name": "EditorFrame",
3056
+ "category": "Structure",
3057
+ "kind": "component",
3058
+ "source": "kerf",
3059
+ "description": "A bounded editor surface with a fixed toolbar and one native source or preview scroll owner.",
3060
+ "uses": [
3061
+ "toolbar"
3062
+ ],
3063
+ "purpose": "Own a framed or embedded writing surface while the app owns its native textarea, draft, and rendered preview.",
3064
+ "publicExports": [
3065
+ "EditorFrame"
3066
+ ],
3067
+ "useWhen": [
3068
+ "A standalone Markdown or text editor needs a fixed toolbar above a bounded native textarea or preview.",
3069
+ "An embedded editor needs the same source/preview anatomy without a second frame."
3070
+ ],
3071
+ "avoidWhen": [
3072
+ "Do not use for an ordinary single form field; use TextField.",
3073
+ "Do not replace the app's draft, recovery, focus, or preview behavior."
3074
+ ],
3075
+ "alternatives": [
3076
+ {
3077
+ "id": "text-field",
3078
+ "relationship": "nearest",
3079
+ "when": "Use TextField for a conventional form value without editor toolbar and preview."
3080
+ }
3081
+ ],
3082
+ "appOwns": [
3083
+ "draft value",
3084
+ "editing actions",
3085
+ "preview rendering",
3086
+ "recovery and focus management"
3087
+ ],
3088
+ "variants": [
3089
+ "source and preview modes",
3090
+ "framed and embedded appearances",
3091
+ "padded and flush insets",
3092
+ "expanded overlay",
3093
+ "embedded autosize or restored source height"
3094
+ ],
3095
+ "accessibility": [
3096
+ "Give the editor frame a label when it is a distinct region; the app labels its native textarea and supplies toolbar controls."
3097
+ ],
3098
+ "geometry": {
3099
+ "margin": "none",
3100
+ "border": "self",
3101
+ "padding": "self"
3102
+ },
3103
+ "delivery": {
3104
+ "browserImport": "@kerfjs/ui/editor-frame",
3105
+ "manualCssImport": "@kerfjs/ui/editor-frame.css",
3106
+ "sideEffects": [
3107
+ "browser-condition-css"
3108
+ ]
3109
+ },
3110
+ "wiring": [],
3111
+ "publicClasses": [
3112
+ "kui-editor-frame",
3113
+ "kui-editor-frame__toolbar",
3114
+ "kui-editor-frame__surface"
3115
+ ],
3116
+ "publicTokens": [
3117
+ "--kui-color-neutral-border-normal",
3118
+ "--kui-color-neutral-on-normal",
3119
+ "--kui-color-surface",
3120
+ "--kui-radius-l",
3121
+ "--kui-shadow-l"
3122
+ ],
3123
+ "links": {
3124
+ "catalogRoute": "?component=editor-frame",
3125
+ "documentation": "docs/editor-frame.md",
3126
+ "recipe": "docs/editor-frame.md"
3127
+ }
3128
+ },
3129
+ {
3130
+ "id": "value-preview",
3131
+ "name": "ValuePreview",
3132
+ "category": "Structure",
3133
+ "kind": "component",
3134
+ "source": "kerf",
3135
+ "description": "A bounded, read-only multiline text value with keyboard scrolling.",
3136
+ "uses": [
3137
+ "sunken-panel"
3138
+ ],
3139
+ "purpose": "Display a literal long value with line breaks and long tokens inside a bounded scroll region.",
3140
+ "publicExports": [
3141
+ "ValuePreview"
3142
+ ],
3143
+ "useWhen": [
3144
+ "A read-only value may have multiple lines or long unbroken text and needs bounded scrolling, such as a conflict version."
3145
+ ],
3146
+ "avoidWhen": [
3147
+ "Do not use for editing; use a multiline TextField.",
3148
+ "Do not use for short label/value facts; use ValueTable."
3149
+ ],
3150
+ "alternatives": [
3151
+ {
3152
+ "id": "value-table",
3153
+ "relationship": "nearest",
3154
+ "when": "Use ValueTable for rows of short labelled facts."
3155
+ }
3156
+ ],
3157
+ "appOwns": [
3158
+ "value",
3159
+ "empty wording"
3160
+ ],
3161
+ "variants": [
3162
+ "typed minimum and maximum height"
3163
+ ],
3164
+ "accessibility": [
3165
+ "Give the focusable output a label so keyboard and screen-reader users can identify and scroll it."
3166
+ ],
3167
+ "geometry": {
3168
+ "margin": "none",
3169
+ "border": "none",
3170
+ "padding": "none"
3171
+ },
3172
+ "delivery": {
3173
+ "browserImport": "@kerfjs/ui/value-preview",
3174
+ "manualCssImport": "@kerfjs/ui/value-preview.css",
3175
+ "sideEffects": [
3176
+ "browser-condition-css"
3177
+ ]
3178
+ },
3179
+ "wiring": [],
3180
+ "publicClasses": [
3181
+ "kui-value-preview"
3182
+ ],
3183
+ "publicTokens": [
3184
+ "--kui-color-text",
3185
+ "--kui-font-xs",
3186
+ "--kui-focus-ring"
3187
+ ],
3188
+ "links": {
3189
+ "catalogRoute": "?component=value-preview",
3190
+ "documentation": "docs/value-preview.md",
3191
+ "recipe": "docs/value-preview.md"
3192
+ }
3193
+ },
3049
3194
  {
3050
3195
  "id": "value-table",
3051
3196
  "name": "ValueTable",
@@ -3221,14 +3366,15 @@
3221
3366
  "category": "Structure",
3222
3367
  "kind": "component",
3223
3368
  "source": "kerf",
3224
- "description": "A selectable, focusable summary of one item as a list row or a board card, with a state rail, a clamped title, trailing meta, and a metadata line.",
3369
+ "description": "A selectable option or static diagnostic group as a list row or board card, with a state rail, a clamped title, trailing meta, and a metadata line.",
3225
3370
  "uses": [],
3226
3371
  "purpose": "Present dense item summaries (tickets, tasks, messages) as selectable list rows or board cards with Kerf-owned selection, hover, focus, rail, clamping, list-edge rounding, and narrow compaction, without app row CSS.",
3227
3372
  "publicExports": [
3228
3373
  "SummaryRow"
3229
3374
  ],
3230
3375
  "useWhen": [
3231
- "A list or board shows many items as dense, selectable summaries with an identifier, a title, a timestamp, and metadata."
3376
+ "A list or board shows many items as dense, selectable summaries with an identifier, a title, a timestamp, and metadata.",
3377
+ "An unreadable item needs the same summary chrome with a nested recovery action but must be a labeled non-option group."
3232
3378
  ],
3233
3379
  "avoidWhen": [
3234
3380
  "Do not use for navigation rows with a single label; use ListItem.",
@@ -3260,10 +3406,12 @@
3260
3406
  "state rail",
3261
3407
  "list edge start, end, or only",
3262
3408
  "title line budget",
3263
- "deferred rendering"
3409
+ "deferred rendering",
3410
+ "static labeled group with a nested action (semanticRole=group)",
3411
+ "native draggable option root for list rows and board cards"
3264
3412
  ],
3265
3413
  "accessibility": [
3266
- "Each SummaryRow is role=option with tabindex=0 and aria-selected; place rows in a labeled role=listbox and move focus with the arrow keys in the app. The rail is decorative; carry its meaning in text."
3414
+ "By default SummaryRow is role=option with tabindex=0 and aria-selected; place options in a labeled role=listbox and move focus with the arrow keys in the app. semanticRole=group requires ariaLabel and omits the root Tab stop and aria-selected, so a nested native action owns focus and activation. The rail is decorative; carry its meaning in text."
3267
3415
  ],
3268
3416
  "geometry": {
3269
3417
  "margin": "none",
@@ -4964,7 +5112,8 @@
4964
5112
  "chat bubble at the start or end of the row (bubble), fit to its content up to 80%",
4965
5113
  "named focus-target region",
4966
5114
  "multi-select rich grid row",
4967
- "gridRow: a non-selectable row (role=\"row\" with a gridcell) between selectable rows of a multi-select List or Grid"
5115
+ "gridRow: a non-selectable row (role=\"row\" with a gridcell) between selectable rows of a multi-select List or Grid",
5116
+ "image-led feed media with opt-in deferOffscreen hero paint deferral"
4968
5117
  ],
4969
5118
  "accessibility": [
4970
5119
  "An ordinary item adds no role or name; pass ariaLabel only when the item is a distinct named region (it then renders role=\"region\").",
@@ -7580,6 +7729,7 @@
7580
7729
  "A dialog footer, a form, or content needs an action button: tone=\"brand\" for the main action, tone=\"success\" for a positive action such as a pricing check, tone=\"danger\" for a destructive one, and emphasis=\"secondary\" or \"plain\" for the quieter actions beside it.",
7581
7730
  "A form submits or resets: type=\"submit\" or \"reset\" (the default type never submits), with name, value, and form when needed.",
7582
7731
  "An action navigates: href renders a link with the same chrome.",
7732
+ "A setup or progress sequence uses a Button as its current step: currentStep marks that button or link with aria-current=\"step\" without changing its styling.",
7583
7733
  "A single primary action spans its form, sheet, or narrow column: fill makes it span the parent's available inline width, label centered, as SplitButton's fill does."
7584
7734
  ],
7585
7735
  "avoidWhen": [
@@ -7600,6 +7750,7 @@
7600
7750
  ],
7601
7751
  "appOwns": [
7602
7752
  "the label and its delegated action",
7753
+ "which step is current when currentStep marks a sequence",
7603
7754
  "what a press does while loading",
7604
7755
  "form ownership"
7605
7756
  ],
@@ -7612,6 +7763,7 @@
7612
7763
  "leading icon",
7613
7764
  "icon-only square (icon and ariaLabel, no label)",
7614
7765
  "pressed toggle (aria-pressed) on any tone and emphasis",
7766
+ "currentStep marker (aria-current=\"step\") on a button or link",
7615
7767
  "loading",
7616
7768
  "disabled",
7617
7769
  "link (href)",
@@ -7621,7 +7773,8 @@
7621
7773
  "accessibility": [
7622
7774
  "A native <button> (type=\"button\" unless set) or, with href, an <a>; its label names it, or ariaLabel when the label is not enough. Focus shows the standard focus ring 1px outside the box; with focusRingInset it is drawn inside the box, two-tone with a surface-colored line inside the ring color, so it keeps 3:1 contrast on a loud fill of its own hue.",
7623
7775
  "Loading shows a spinner in place of the label, which still names the button, and sets aria-busy=\"true\". A disabled link drops its href and reports aria-disabled=\"true\". Disabled success uses a quiet success fill and readable on-fill text instead of fading the label.",
7624
- "With pressed, it is a toggle: aria-pressed reports the state, and the pressed look adds a doubled outline (secondary and plain) or a darkened fill with an inset ring (primary), so the state never relies on hue alone; forced-colors mode paints it Highlight. An icon-only Button is named by ariaLabel, and help tags show that name on hover and keyboard focus. A title gives native hover text that says more than the name and replaces the help tag."
7776
+ "With pressed, it is a toggle: aria-pressed reports the state, and the pressed look adds a doubled outline (secondary and plain) or a darkened fill with an inset ring (primary), so the state never relies on hue alone; forced-colors mode paints it Highlight. An icon-only Button is named by ariaLabel, and help tags show that name on hover and keyboard focus. A title gives native hover text that says more than the name and replaces the help tag.",
7777
+ "With currentStep, a native button or link reports aria-current=\"step\" even when disabled; the app owns the step label and which step is current."
7625
7778
  ],
7626
7779
  "geometry": {
7627
7780
  "margin": "none",
@@ -7671,7 +7824,7 @@
7671
7824
  "rootElement": "button",
7672
7825
  "links": {
7673
7826
  "catalogRoute": "?component=button",
7674
- "documentation": "docs/component-selection.md",
7827
+ "documentation": "docs/button.md",
7675
7828
  "recipe": "docs/component-selection.md#problem-to-component-matrix"
7676
7829
  }
7677
7830
  },
@@ -2059,6 +2059,7 @@
2059
2059
  "obligations": [
2060
2060
  "DialogSurface is a native <dialog> opened modally (showModal): it is in the top layer, the rest of the page is inert, focus stays inside, and it returns to the opener on close. Focus starts on an autofocus element, otherwise on the panel.",
2061
2061
  "Name it with label (also its title text) or labelledBy (the id of a heading, such as a ToolbarText). The close button is named Close (closeLabel) and Escape asks to close; the cancelable kui-request-close event reports source close-button, escape, or backdrop.",
2062
+ "closeEmphasis=\"quiet\" uses a smaller, quiet-colored close glyph inside the same 40px target; hover, focus ring, forced colors, accessible naming, and close requests remain available.",
2062
2063
  "Name a PopoverDialogSurface with label or labelledBy. Describe either surface with describedBy (rendered as aria-describedby on the dialog root), usually the heading ToolbarText descriptionId or app-owned summary copy in the body; rootAttributes accepts only data-* attributes, so it never sets aria-describedby.",
2063
2064
  "A floating header's title uses the normal text color, which keeps WCAG AA over the 80% raised chrome material on any content; the material turns opaque under prefers-reduced-transparency, prefers-contrast: more, forced colors, or without backdrop-filter.",
2064
2065
  "A resizable bottom sheet's grabber is a focusable horizontal separator (\"Resize sheet\") with aria-valuemin, aria-valuenow, and aria-valuemax in visible px; the arrow keys, Page Up/Down, Home, and End resize it, and its slide motion is skipped under prefers-reduced-motion.",
@@ -2708,6 +2709,116 @@
2708
2709
  "composition": "generated-permissive-default"
2709
2710
  }
2710
2711
  },
2712
+ {
2713
+ "key": "@kerfjs/ui:editor-frame",
2714
+ "package": "@kerfjs/ui",
2715
+ "id": "editor-frame",
2716
+ "name": "EditorFrame",
2717
+ "kind": "component",
2718
+ "source": "kerf",
2719
+ "parents": { "mode": "any", "entries": [] },
2720
+ "contexts": ["application-ui"],
2721
+ "zones": [],
2722
+ "children": { "mode": "any", "concepts": [], "requiredConcepts": [] },
2723
+ "state": [
2724
+ { "id": "draft value", "owner": "application", "required": false },
2725
+ { "id": "editing actions", "owner": "application", "required": false },
2726
+ {
2727
+ "id": "preview rendering",
2728
+ "owner": "application",
2729
+ "required": false
2730
+ },
2731
+ {
2732
+ "id": "recovery and focus management",
2733
+ "owner": "application",
2734
+ "required": false
2735
+ }
2736
+ ],
2737
+ "wiring": {
2738
+ "required": false,
2739
+ "helpers": [],
2740
+ "obligations": [],
2741
+ "stateAttributes": []
2742
+ },
2743
+ "responsive": { "owner": "not-applicable", "behaviors": [] },
2744
+ "layout": {
2745
+ "roles": ["structure"],
2746
+ "geometry": { "margin": "none", "border": "self", "padding": "self" }
2747
+ },
2748
+ "accessibility": {
2749
+ "obligations": [
2750
+ "Give the editor frame a label when it is a distinct region; the app labels its native textarea and supplies toolbar controls."
2751
+ ]
2752
+ },
2753
+ "cssValueProps": [],
2754
+ "boundaries": {
2755
+ "rootClass": "kui-editor-frame",
2756
+ "publicClasses": [
2757
+ "kui-editor-frame",
2758
+ "kui-editor-frame__toolbar",
2759
+ "kui-editor-frame__surface"
2760
+ ],
2761
+ "publicTokens": [
2762
+ "--kui-color-neutral-border-normal",
2763
+ "--kui-color-neutral-on-normal",
2764
+ "--kui-color-surface",
2765
+ "--kui-radius-l",
2766
+ "--kui-shadow-l"
2767
+ ]
2768
+ },
2769
+ "diagnostics": [],
2770
+ "provenance": {
2771
+ "selection": "ai/component-catalog.json",
2772
+ "composition": "generated-permissive-default"
2773
+ }
2774
+ },
2775
+ {
2776
+ "key": "@kerfjs/ui:value-preview",
2777
+ "package": "@kerfjs/ui",
2778
+ "id": "value-preview",
2779
+ "name": "ValuePreview",
2780
+ "kind": "component",
2781
+ "source": "kerf",
2782
+ "parents": { "mode": "any", "entries": [] },
2783
+ "contexts": ["application-ui"],
2784
+ "zones": [],
2785
+ "children": { "mode": "any", "concepts": [], "requiredConcepts": [] },
2786
+ "state": [
2787
+ { "id": "value", "owner": "application", "required": false },
2788
+ { "id": "empty wording", "owner": "application", "required": false }
2789
+ ],
2790
+ "wiring": {
2791
+ "required": false,
2792
+ "helpers": [],
2793
+ "obligations": [],
2794
+ "stateAttributes": []
2795
+ },
2796
+ "responsive": { "owner": "not-applicable", "behaviors": [] },
2797
+ "layout": {
2798
+ "roles": ["structure"],
2799
+ "geometry": { "margin": "none", "border": "none", "padding": "none" }
2800
+ },
2801
+ "accessibility": {
2802
+ "obligations": [
2803
+ "Give the focusable output a label so keyboard and screen-reader users can identify and scroll it."
2804
+ ]
2805
+ },
2806
+ "cssValueProps": [],
2807
+ "boundaries": {
2808
+ "rootClass": "kui-value-preview",
2809
+ "publicClasses": ["kui-value-preview"],
2810
+ "publicTokens": [
2811
+ "--kui-color-text",
2812
+ "--kui-font-xs",
2813
+ "--kui-focus-ring"
2814
+ ]
2815
+ },
2816
+ "diagnostics": [],
2817
+ "provenance": {
2818
+ "selection": "ai/component-catalog.json",
2819
+ "composition": "generated-permissive-default"
2820
+ }
2821
+ },
2711
2822
  {
2712
2823
  "key": "@kerfjs/ui:value-table",
2713
2824
  "package": "@kerfjs/ui",
@@ -2893,7 +3004,7 @@
2893
3004
  },
2894
3005
  "accessibility": {
2895
3006
  "obligations": [
2896
- "Each SummaryRow is role=option with tabindex=0 and aria-selected; place rows in a labeled role=listbox and move focus with the arrow keys in the app. The rail is decorative; carry its meaning in text."
3007
+ "By default SummaryRow is role=option with tabindex=0 and aria-selected; place options in a labeled role=listbox and move focus with the arrow keys in the app. semanticRole=group requires ariaLabel and omits the root Tab stop and aria-selected, so a nested native action owns focus and activation. The rail is decorative; carry its meaning in text."
2897
3008
  ]
2898
3009
  },
2899
3010
  "cssValueProps": [],
@@ -6073,6 +6184,11 @@
6073
6184
  "owner": "application",
6074
6185
  "required": false
6075
6186
  },
6187
+ {
6188
+ "id": "which step is current when currentStep marks a sequence",
6189
+ "owner": "application",
6190
+ "required": false
6191
+ },
6076
6192
  {
6077
6193
  "id": "what a press does while loading",
6078
6194
  "owner": "application",
@@ -6095,7 +6211,8 @@
6095
6211
  "obligations": [
6096
6212
  "A native <button> (type=\"button\" unless set) or, with href, an <a>; its label names it, or ariaLabel when the label is not enough. Focus shows the standard focus ring 1px outside the box; with focusRingInset it is drawn inside the box, two-tone with a surface-colored line inside the ring color, so it keeps 3:1 contrast on a loud fill of its own hue.",
6097
6213
  "Loading shows a spinner in place of the label, which still names the button, and sets aria-busy=\"true\". A disabled link drops its href and reports aria-disabled=\"true\". Disabled success uses a quiet success fill and readable on-fill text instead of fading the label.",
6098
- "With pressed, it is a toggle: aria-pressed reports the state, and the pressed look adds a doubled outline (secondary and plain) or a darkened fill with an inset ring (primary), so the state never relies on hue alone; forced-colors mode paints it Highlight. An icon-only Button is named by ariaLabel, and help tags show that name on hover and keyboard focus. A title gives native hover text that says more than the name and replaces the help tag."
6214
+ "With pressed, it is a toggle: aria-pressed reports the state, and the pressed look adds a doubled outline (secondary and plain) or a darkened fill with an inset ring (primary), so the state never relies on hue alone; forced-colors mode paints it Highlight. An icon-only Button is named by ariaLabel, and help tags show that name on hover and keyboard focus. A title gives native hover text that says more than the name and replaces the help tag.",
6215
+ "With currentStep, a native button or link reports aria-current=\"step\" even when disabled; the app owns the step label and which step is current."
6099
6216
  ]
6100
6217
  },
6101
6218
  "cssValueProps": [],
@@ -35,6 +35,8 @@ Exact prop and callback types live in [`public-api-signatures-v1.md`](../public-
35
35
  - [ToolbarText](./toolbar-text.md) — Extra-large through extra-small toolbar identity text.
36
36
  - [Text](./text.md) — Render paragraph semantics by default, h1 through h6 when selected, or geometry-free inline secondary text as a span while independently configuring quiet or danger tone, compact through extra-large size, or monospace font.
37
37
  - [Headers](./headers.md) — Page, dialog, and definition-list hierarchy.
38
+ - [EditorFrame](./editor-frame.md) — Own a framed or embedded writing surface while the app owns its native textarea, draft, and rendered preview.
39
+ - [ValuePreview](./value-preview.md) — Display a literal long value with line breaks and long tokens inside a bounded scroll region.
38
40
  - [ValueTable](./value-table.md) — Key/value rows with optional icons and aligned separators.
39
41
  - [FramedList](./framed-list.md) — Group settings-style rows of arbitrary content (copy, keycaps, controls) in one framed surface with between-row dividers and an optional highlighted row, without app list CSS.
40
42
  - [SummaryRow](./summary-row.md) — Present dense item summaries (tickets, tasks, messages) as selectable list rows or board cards with Kerf-owned selection, hover, focus, rail, clamping, list-edge rounding, and narrow compaction, without app row CSS.
@@ -43,6 +43,7 @@ Exact prop names and types: [`@kerfjs/ui/backdrop-image`](../public-api-signatur
43
43
  - fade into a color that also paints below
44
44
  - bottom or full scrim under overlay content
45
45
  - image running beneath clear or overlay chrome (underChrome)
46
+ - opt-in deferOffscreen hero paint deferral for dense image feeds without changing reserved height
46
47
 
47
48
  ## Composition
48
49
 
@@ -13,6 +13,7 @@ Trigger an action from a dialog footer, a form, or content outside any Toolbar,
13
13
  - A dialog footer, a form, or content needs an action button: tone="brand" for the main action, tone="success" for a positive action such as a pricing check, tone="danger" for a destructive one, and emphasis="secondary" or "plain" for the quieter actions beside it.
14
14
  - A form submits or resets: type="submit" or "reset" (the default type never submits), with name, value, and form when needed.
15
15
  - An action navigates: href renders a link with the same chrome.
16
+ - A setup or progress sequence uses a Button as its current step: currentStep marks that button or link with aria-current="step" without changing its styling.
16
17
  - A single primary action spans its form, sheet, or narrow column: fill makes it span the parent's available inline width, label centered, as SplitButton's fill does.
17
18
 
18
19
  **Not when:**
@@ -46,6 +47,7 @@ Exact prop names and types: [`@kerfjs/ui/button`](../public-api-signatures-v1.md
46
47
  - leading icon
47
48
  - icon-only square (icon and ariaLabel, no label)
48
49
  - pressed toggle (aria-pressed) on any tone and emphasis
50
+ - currentStep marker (aria-current="step") on a button or link
49
51
  - loading
50
52
  - disabled
51
53
  - link (href)
@@ -61,7 +63,7 @@ Exact prop names and types: [`@kerfjs/ui/button`](../public-api-signatures-v1.md
61
63
 
62
64
  ## State and wiring
63
65
 
64
- **The app owns:** the label and its delegated action; what a press does while loading; form ownership.
66
+ **The app owns:** the label and its delegated action; which step is current when currentStep marks a sequence; what a press does while loading; form ownership.
65
67
 
66
68
  No wiring helper.
67
69
 
@@ -74,6 +76,7 @@ Margin: none · border: self · padding: self (layout role: controls). `self` me
74
76
  - A native \<button> (type="button" unless set) or, with href, an \<a>; its label names it, or ariaLabel when the label is not enough. Focus shows the standard focus ring 1px outside the box; with focusRingInset it is drawn inside the box, two-tone with a surface-colored line inside the ring color, so it keeps 3:1 contrast on a loud fill of its own hue.
75
77
  - Loading shows a spinner in place of the label, which still names the button, and sets aria-busy="true". A disabled link drops its href and reports aria-disabled="true". Disabled success uses a quiet success fill and readable on-fill text instead of fading the label.
76
78
  - With pressed, it is a toggle: aria-pressed reports the state, and the pressed look adds a doubled outline (secondary and plain) or a darkened fill with an inset ring (primary), so the state never relies on hue alone; forced-colors mode paints it Highlight. An icon-only Button is named by ariaLabel, and help tags show that name on hover and keyboard focus. A title gives native hover text that says more than the name and replaces the help tag.
79
+ - With currentStep, a native button or link reports aria-current="step" even when disabled; the app owns the step label and which step is current.
77
80
 
78
81
  ## Styling boundary
79
82
 
@@ -90,5 +93,5 @@ Everything else is internal: `kerf-ui-analyze` reports private or unknown `.kui-
90
93
  ## Related
91
94
 
92
95
  - Components: [LoadingSpinner](./loading-spinner.md), [Button](./wa-button.md), [ToolbarControlGroup](./toolbar-control-group.md).
93
- - Docs: [`docs/component-selection.md`](../../docs/component-selection.md), [`docs/component-selection.md#problem-to-component-matrix`](../../docs/component-selection.md#problem-to-component-matrix).
96
+ - Docs: [`docs/button.md`](../../docs/button.md), [`docs/component-selection.md#problem-to-component-matrix`](../../docs/component-selection.md#problem-to-component-matrix).
94
97
  - UX catalog route: `?component=button`.
@@ -53,6 +53,7 @@ Exact prop names and types: [`@kerfjs/ui/content-item`](../public-api-signatures
53
53
  - named focus-target region
54
54
  - multi-select rich grid row
55
55
  - gridRow: a non-selectable row (role="row" with a gridcell) between selectable rows of a multi-select List or Grid
56
+ - image-led feed media with opt-in deferOffscreen hero paint deferral
56
57
 
57
58
  ## Composition
58
59
 
@@ -0,0 +1,80 @@
1
+ # EditorFrame
2
+
3
+ <!-- Generated by scripts/sync-component-docs.mjs from ai/component-catalog.json and ai/component-composition.json. Do not edit; run `npm run catalog:sync`. -->
4
+
5
+ `@kerfjs/ui:editor-frame` · Kerf UI · component · Structure
6
+
7
+ A bounded editor surface with a fixed toolbar and one native source or preview scroll owner.
8
+
9
+ ## When to use
10
+
11
+ Own a framed or embedded writing surface while the app owns its native textarea, draft, and rendered preview.
12
+
13
+ - A standalone Markdown or text editor needs a fixed toolbar above a bounded native textarea or preview.
14
+ - An embedded editor needs the same source/preview anatomy without a second frame.
15
+
16
+ **Not when:**
17
+
18
+ - Do not use for an ordinary single form field; use TextField.
19
+ - Do not replace the app's draft, recovery, focus, or preview behavior.
20
+
21
+ **Alternatives:**
22
+
23
+ - [TextField](./text-field.md) (nearest) — Use TextField for a conventional form value without editor toolbar and preview.
24
+
25
+ ## Imports
26
+
27
+ - JavaScript: `@kerfjs/ui/editor-frame` — exports `EditorFrame`.
28
+ - CSS: the browser build of `@kerfjs/ui/editor-frame` pulls its CSS automatically; import `@kerfjs/ui/editor-frame.css` manually only without the `browser` export condition.
29
+ - Declared side effects: browser-condition-css.
30
+
31
+ ## Props
32
+
33
+ Exact prop names and types: [`@kerfjs/ui/editor-frame`](../public-api-signatures-v1.md#kerfjsuieditor-frame) in `public-api-signatures-v1.md`. Do not infer props from examples.
34
+
35
+ **Variants (configure, do not restyle):**
36
+
37
+ - source and preview modes
38
+ - framed and embedded appearances
39
+ - padded and flush insets
40
+ - expanded overlay
41
+ - embedded autosize or restored source height
42
+
43
+ ## Composition
44
+
45
+ - Parents: any.
46
+ - Contexts: `application-ui`.
47
+ - Children: unrestricted.
48
+ - Built from: [Toolbar](./toolbar.md).
49
+
50
+ ## State and wiring
51
+
52
+ **The app owns:** draft value; editing actions; preview rendering; recovery and focus management.
53
+
54
+ No wiring helper.
55
+
56
+ ## Geometry
57
+
58
+ Margin: none · border: self · padding: self (layout role: structure). `self` means the component already owns it — do not add wrapper padding, margin, or borders around it.
59
+
60
+ ## Accessibility
61
+
62
+ - Give the editor frame a label when it is a distinct region; the app labels its native textarea and supplies toolbar controls.
63
+
64
+ ## Styling boundary
65
+
66
+ The component owns its own styles. Configure it through its props and variants; do not override its internals.
67
+
68
+ Public class hooks (select for layout placement only, never to change the component's look): `kui-editor-frame`, `kui-editor-frame__toolbar`, `kui-editor-frame__surface`.
69
+
70
+ Never put `kui-editor-frame`, `kui-editor-frame__toolbar`, `kui-editor-frame__surface` on an element you write; render `EditorFrame` instead (`KUI-L103`).
71
+
72
+ Public tokens it reads: `--kui-color-neutral-border-normal`, `--kui-color-neutral-on-normal`, `--kui-color-surface`, `--kui-radius-l`, `--kui-shadow-l`. Set a token only where its public contract allows; prefer a prop.
73
+
74
+ Everything else is internal: `kerf-ui-analyze` reports private or unknown `.kui-*` selectors (`KUI-L001`, `KUI-L010`), unknown or private tokens (`KUI-L002`, `KUI-L012`), and any application rule that restyles the component, forced dimensions included (`KUI-L019`).
75
+
76
+ ## Related
77
+
78
+ - Components: [Toolbar](./toolbar.md), [TextField](./text-field.md).
79
+ - Docs: [`docs/editor-frame.md`](../../docs/editor-frame.md).
80
+ - UX catalog route: `?component=editor-frame`.
@@ -4,13 +4,14 @@
4
4
 
5
5
  `@kerfjs/ui:summary-row` · Kerf UI · component · Structure
6
6
 
7
- A selectable, focusable summary of one item as a list row or a board card, with a state rail, a clamped title, trailing meta, and a metadata line.
7
+ A selectable option or static diagnostic group as a list row or board card, with a state rail, a clamped title, trailing meta, and a metadata line.
8
8
 
9
9
  ## When to use
10
10
 
11
11
  Present dense item summaries (tickets, tasks, messages) as selectable list rows or board cards with Kerf-owned selection, hover, focus, rail, clamping, list-edge rounding, and narrow compaction, without app row CSS.
12
12
 
13
13
  - A list or board shows many items as dense, selectable summaries with an identifier, a title, a timestamp, and metadata.
14
+ - An unreadable item needs the same summary chrome with a nested recovery action but must be a labeled non-option group.
14
15
 
15
16
  **Not when:**
16
17
 
@@ -43,6 +44,8 @@ Exact prop names and types: [`@kerfjs/ui/summary-row`](../public-api-signatures-
43
44
  - list edge start, end, or only
44
45
  - title line budget
45
46
  - deferred rendering
47
+ - static labeled group with a nested action (semanticRole=group)
48
+ - native draggable option root for list rows and board cards
46
49
 
47
50
  ## Composition
48
51
 
@@ -64,7 +67,7 @@ Margin: none · border: self · padding: self (layout role: structure). `self` m
64
67
 
65
68
  ## Accessibility
66
69
 
67
- - Each SummaryRow is role=option with tabindex=0 and aria-selected; place rows in a labeled role=listbox and move focus with the arrow keys in the app. The rail is decorative; carry its meaning in text.
70
+ - By default SummaryRow is role=option with tabindex=0 and aria-selected; place options in a labeled role=listbox and move focus with the arrow keys in the app. semanticRole=group requires ariaLabel and omits the root Tab stop and aria-selected, so a nested native action owns focus and activation. The rail is decorative; carry its meaning in text.
68
71
 
69
72
  ## Styling boundary
70
73