@kerfjs/ui 5.0.0-beta.7 → 5.0.0-beta.71
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.
- package/README.md +501 -139
- package/ai/application-ui-diagnostic-ids-v1.json +109 -0
- package/ai/application-ui-profile-sync.cjs +548 -0
- package/ai/application-ui-profile.d.mts +1 -0
- package/ai/application-ui-profile.d.ts +106 -0
- package/ai/application-ui-profile.defaults.json +96 -0
- package/ai/application-ui-profile.mjs +653 -0
- package/ai/application-ui-profile.schema.json +140 -0
- package/ai/catalog-authoring.json +36 -0
- package/ai/catalog-authoring.schema.json +109 -0
- package/ai/compile-time-contracts-v1.json +149 -0
- package/ai/compile-time-contracts-v1.schema.json +52 -0
- package/ai/component-catalog-extension.schema.json +104 -0
- package/ai/component-catalog.json +3905 -446
- package/ai/component-catalog.schema.json +94 -1
- package/ai/component-class-owners.cjs +83 -0
- package/ai/component-composition-extension.d.ts +7 -0
- package/ai/component-composition-extension.schema.json +7 -0
- package/ai/component-composition-rules.json +524 -0
- package/ai/component-composition.d.ts +129 -0
- package/ai/component-composition.json +7140 -0
- package/ai/component-composition.schema.json +408 -0
- package/ai/components/README.md +171 -0
- package/ai/components/application-tabs.md +65 -0
- package/ai/components/badge.md +72 -0
- package/ai/components/chip.md +72 -0
- package/ai/components/collapsible-panel.md +102 -0
- package/ai/components/content-item.md +93 -0
- package/ai/components/disclosure-arrow.md +77 -0
- package/ai/components/document-baseline.md +75 -0
- package/ai/components/empty-state.md +80 -0
- package/ai/components/feedback.md +71 -0
- package/ai/components/floating-toolbar.md +82 -0
- package/ai/components/foundation.md +65 -0
- package/ai/components/grid.md +98 -0
- package/ai/components/headers.md +63 -0
- package/ai/components/layout.md +75 -0
- package/ai/components/list-action-row.md +91 -0
- package/ai/components/list-header.md +89 -0
- package/ai/components/list-inset-control.md +74 -0
- package/ai/components/list-inset-text.md +72 -0
- package/ai/components/list-item.md +94 -0
- package/ai/components/list.md +105 -0
- package/ai/components/loading-spinner.md +73 -0
- package/ai/components/lucide-icon.md +78 -0
- package/ai/components/nav-stack.md +104 -0
- package/ai/components/pane.md +101 -0
- package/ai/components/popup-menu.md +171 -0
- package/ai/components/recipe-app-shell.md +57 -0
- package/ai/components/recipe-collapsible-sidebar.md +57 -0
- package/ai/components/recipe-compact-toolbar.md +57 -0
- package/ai/components/recipe-composer-form.md +57 -0
- package/ai/components/recipe-list-detail-dialog.md +57 -0
- package/ai/components/recipe-list-workspace-states.md +57 -0
- package/ai/components/recipe-loading-inspector.md +57 -0
- package/ai/components/recipe-navigation-sidebar.md +57 -0
- package/ai/components/recipe-navigation-stack.md +57 -0
- package/ai/components/recipe-workspace-header.md +57 -0
- package/ai/components/resize.md +106 -0
- package/ai/components/row.md +96 -0
- package/ai/components/segmented-control.md +82 -0
- package/ai/components/select.md +113 -0
- package/ai/components/skeleton.md +84 -0
- package/ai/components/spacer.md +86 -0
- package/ai/components/split-view.md +102 -0
- package/ai/components/state-banner.md +94 -0
- package/ai/components/sunken-panel.md +79 -0
- package/ai/components/surface-scaffold.md +78 -0
- package/ai/components/tab-bar.md +113 -0
- package/ai/components/tab-scaffold.md +94 -0
- package/ai/components/tabs.md +95 -0
- package/ai/components/text.md +85 -0
- package/ai/components/token-search-field.md +106 -0
- package/ai/components/toolbar-control-group.md +102 -0
- package/ai/components/toolbar-text.md +79 -0
- package/ai/components/toolbar.md +97 -0
- package/ai/components/value-table.md +73 -0
- package/ai/components/wa-accordion-item.md +55 -0
- package/ai/components/wa-accordion.md +65 -0
- package/ai/components/wa-animated-image.md +55 -0
- package/ai/components/wa-animation.md +55 -0
- package/ai/components/wa-avatar.md +55 -0
- package/ai/components/wa-badge.md +55 -0
- package/ai/components/wa-breadcrumb-item.md +53 -0
- package/ai/components/wa-breadcrumb.md +55 -0
- package/ai/components/wa-button-group.md +59 -0
- package/ai/components/wa-button.md +55 -0
- package/ai/components/wa-callout.md +58 -0
- package/ai/components/wa-card.md +59 -0
- package/ai/components/wa-carousel-item.md +53 -0
- package/ai/components/wa-carousel.md +55 -0
- package/ai/components/wa-checkbox-group.md +55 -0
- package/ai/components/wa-checkbox.md +55 -0
- package/ai/components/wa-color-picker.md +55 -0
- package/ai/components/wa-comparison.md +55 -0
- package/ai/components/wa-copy-button.md +55 -0
- package/ai/components/wa-details.md +61 -0
- package/ai/components/wa-dialog.md +59 -0
- package/ai/components/wa-divider.md +53 -0
- package/ai/components/wa-drawer.md +55 -0
- package/ai/components/wa-dropdown-item.md +60 -0
- package/ai/components/wa-dropdown.md +60 -0
- package/ai/components/wa-format-bytes.md +53 -0
- package/ai/components/wa-format-date.md +53 -0
- package/ai/components/wa-format-number.md +53 -0
- package/ai/components/wa-icon.md +58 -0
- package/ai/components/wa-include.md +53 -0
- package/ai/components/wa-input.md +59 -0
- package/ai/components/wa-intersection-observer.md +53 -0
- package/ai/components/wa-known-date.md +53 -0
- package/ai/components/wa-markdown.md +53 -0
- package/ai/components/wa-mutation-observer.md +53 -0
- package/ai/components/wa-number-input.md +55 -0
- package/ai/components/wa-option.md +59 -0
- package/ai/components/wa-otp-input.md +53 -0
- package/ai/components/wa-page.md +53 -0
- package/ai/components/wa-pagination.md +55 -0
- package/ai/components/wa-popover.md +55 -0
- package/ai/components/wa-popup.md +53 -0
- package/ai/components/wa-progress-bar.md +53 -0
- package/ai/components/wa-progress-ring.md +53 -0
- package/ai/components/wa-qr-code.md +53 -0
- package/ai/components/wa-radio-group.md +59 -0
- package/ai/components/wa-radio.md +55 -0
- package/ai/components/wa-random-content.md +53 -0
- package/ai/components/wa-rating.md +55 -0
- package/ai/components/wa-relative-time.md +53 -0
- package/ai/components/wa-resize-observer.md +53 -0
- package/ai/components/wa-scroller.md +53 -0
- package/ai/components/wa-select.md +63 -0
- package/ai/components/wa-skeleton.md +53 -0
- package/ai/components/wa-slider.md +55 -0
- package/ai/components/wa-spinner.md +58 -0
- package/ai/components/wa-split-panel.md +58 -0
- package/ai/components/wa-switch.md +53 -0
- package/ai/components/wa-tab-group.md +69 -0
- package/ai/components/wa-tab-panel.md +60 -0
- package/ai/components/wa-tab.md +59 -0
- package/ai/components/wa-tag.md +59 -0
- package/ai/components/wa-textarea.md +53 -0
- package/ai/components/wa-time-input.md +55 -0
- package/ai/components/wa-toast-item.md +60 -0
- package/ai/components/wa-toast.md +60 -0
- package/ai/components/wa-tooltip.md +55 -0
- package/ai/components/wa-tree-item.md +55 -0
- package/ai/components/wa-tree.md +55 -0
- package/ai/components/wa-zoomable-frame.md +55 -0
- package/ai/components/workbench.md +118 -0
- package/ai/public-api-signatures-v1.md +2748 -210
- package/ai/skill.md +286 -66
- package/ai/webawesome-jsx-signatures-v1.md +9 -2
- package/analyzer/cli.mjs +49 -0
- package/analyzer/component-ownership.d.mts +72 -0
- package/analyzer/component-ownership.mjs +401 -0
- package/analyzer/index.d.mts +53 -0
- package/analyzer/index.mjs +2307 -0
- package/analyzer/loud-pairs.d.mts +49 -0
- package/analyzer/loud-pairs.mjs +357 -0
- package/analyzer/report.schema.json +66 -0
- package/analyzer/selectors.d.mts +21 -0
- package/analyzer/selectors.mjs +212 -0
- package/catalog-projection-cli.mjs +96 -0
- package/catalog-projection.d.mts +55 -0
- package/catalog-projection.mjs +62 -0
- package/dist/app-tab.d.ts +39 -4
- package/dist/app-tab.js +2 -1
- package/dist/badge.d.ts +52 -0
- package/dist/badge.js +3 -0
- package/dist/badge.js.map +1 -0
- package/dist/browser/badge.js +3 -0
- package/dist/browser/catalog.js +26 -0
- package/dist/browser/chip.js +3 -0
- package/dist/browser/collapsible-panel.js +9 -0
- package/dist/browser/content-item.js +3 -0
- package/dist/browser/floating-toolbar.js +3 -0
- package/dist/browser/grid.js +3 -0
- package/dist/browser/list-action-row.js +5 -0
- package/dist/browser/{menu-header.js → list-header.js} +4 -2
- package/dist/browser/list-inset-control.js +3 -0
- package/dist/browser/list-inset-text.js +3 -0
- package/dist/browser/list-item.js +5 -0
- package/dist/browser/list.js +3 -0
- package/dist/browser/{panel-header.js → nav-stack.js} +5 -3
- package/dist/browser/pane.js +3 -0
- package/dist/browser/popup-menu.js +3 -0
- package/dist/browser/row.js +3 -0
- package/dist/browser/segmented-control.js +0 -1
- package/dist/browser/select.js +1 -0
- package/dist/browser/spacer.js +3 -0
- package/dist/browser/split-view.js +11 -0
- package/dist/browser/state-banner.js +1 -0
- package/dist/browser/sunken-panel.js +3 -0
- package/dist/browser/surface-scaffold.js +3 -0
- package/dist/browser/tab-scaffold.js +4 -0
- package/dist/browser/text.js +3 -0
- package/dist/browser/toolbar-control-group.js +1 -0
- package/dist/browser/workbench.js +14 -0
- package/dist/catalog-resources.d.ts +48 -0
- package/dist/catalog-resources.js +22 -0
- package/dist/catalog-resources.js.map +1 -0
- package/dist/catalog.d.ts +92 -0
- package/dist/catalog.js +503 -0
- package/dist/catalog.js.map +1 -0
- package/dist/chip.d.ts +35 -0
- package/dist/chip.js +3 -0
- package/dist/chip.js.map +1 -0
- package/dist/chunk-2G2ZJQYK.js +152 -0
- package/dist/chunk-2G2ZJQYK.js.map +1 -0
- package/dist/chunk-3BYFWTP3.js +108 -0
- package/dist/chunk-3BYFWTP3.js.map +1 -0
- package/dist/chunk-47CKKLBE.js +34 -0
- package/dist/chunk-47CKKLBE.js.map +1 -0
- package/dist/chunk-4SQK27MR.js +202 -0
- package/dist/chunk-4SQK27MR.js.map +1 -0
- package/dist/chunk-5I7QGQVT.js +24 -0
- package/dist/chunk-5I7QGQVT.js.map +1 -0
- package/dist/chunk-6DFDTWGG.js +288 -0
- package/dist/chunk-6DFDTWGG.js.map +1 -0
- package/dist/chunk-7OKX3EF6.js +135 -0
- package/dist/chunk-7OKX3EF6.js.map +1 -0
- package/dist/chunk-AHEG27M7.js +66 -0
- package/dist/chunk-AHEG27M7.js.map +1 -0
- package/dist/chunk-AYUHMAI6.js +48 -0
- package/dist/chunk-AYUHMAI6.js.map +1 -0
- package/dist/chunk-B2ZMR7NY.js +85 -0
- package/dist/chunk-B2ZMR7NY.js.map +1 -0
- package/dist/chunk-BFPBS5Y4.js +250 -0
- package/dist/chunk-BFPBS5Y4.js.map +1 -0
- package/dist/chunk-DB5ADJKF.js +775 -0
- package/dist/chunk-DB5ADJKF.js.map +1 -0
- package/dist/chunk-DC6CN2DS.js +123 -0
- package/dist/chunk-DC6CN2DS.js.map +1 -0
- package/dist/chunk-DG6G5EPF.js +35 -0
- package/dist/chunk-DG6G5EPF.js.map +1 -0
- package/dist/{chunk-56RJXPHG.js → chunk-G4S2QU6V.js} +7 -4
- package/dist/chunk-G4S2QU6V.js.map +1 -0
- package/dist/chunk-GLDWRCAM.js +49 -0
- package/dist/chunk-GLDWRCAM.js.map +1 -0
- package/dist/chunk-I3Q7KKYM.js +33 -0
- package/dist/chunk-I3Q7KKYM.js.map +1 -0
- package/dist/chunk-IA7TMD4Y.js +288 -0
- package/dist/chunk-IA7TMD4Y.js.map +1 -0
- package/dist/chunk-J3KGJ6P6.js +135 -0
- package/dist/chunk-J3KGJ6P6.js.map +1 -0
- package/dist/chunk-K47P7QZC.js +17 -0
- package/dist/chunk-K47P7QZC.js.map +1 -0
- package/dist/chunk-K67Y2JNF.js +222 -0
- package/dist/chunk-K67Y2JNF.js.map +1 -0
- package/dist/chunk-KCCEH7RF.js +492 -0
- package/dist/chunk-KCCEH7RF.js.map +1 -0
- package/dist/chunk-KLJKU6ZL.js +109 -0
- package/dist/chunk-KLJKU6ZL.js.map +1 -0
- package/dist/chunk-KVBRMEPM.js +44 -0
- package/dist/chunk-KVBRMEPM.js.map +1 -0
- package/dist/chunk-M6DEEBEF.js +156 -0
- package/dist/chunk-M6DEEBEF.js.map +1 -0
- package/dist/chunk-MEY33VWB.js +44 -0
- package/dist/chunk-MEY33VWB.js.map +1 -0
- package/dist/chunk-MP2GDBQZ.js +50 -0
- package/dist/chunk-MP2GDBQZ.js.map +1 -0
- package/dist/chunk-NC4CFOID.js +188 -0
- package/dist/chunk-NC4CFOID.js.map +1 -0
- package/dist/chunk-NCSWXG7G.js +80 -0
- package/dist/chunk-NCSWXG7G.js.map +1 -0
- package/dist/chunk-NN6GYOA2.js +47 -0
- package/dist/chunk-NN6GYOA2.js.map +1 -0
- package/dist/chunk-OXO2QJWV.js +55 -0
- package/dist/chunk-OXO2QJWV.js.map +1 -0
- package/dist/chunk-P7D3EMXM.js +52 -0
- package/dist/chunk-P7D3EMXM.js.map +1 -0
- package/dist/chunk-PSHUSWQH.js +18 -0
- package/dist/chunk-PSHUSWQH.js.map +1 -0
- package/dist/chunk-Q6YU3BT7.js +138 -0
- package/dist/chunk-Q6YU3BT7.js.map +1 -0
- package/dist/chunk-QJ5VFCW3.js +131 -0
- package/dist/chunk-QJ5VFCW3.js.map +1 -0
- package/dist/chunk-RGS4WX2M.js +44 -0
- package/dist/chunk-RGS4WX2M.js.map +1 -0
- package/dist/chunk-RNQNEENY.js +146 -0
- package/dist/chunk-RNQNEENY.js.map +1 -0
- package/dist/chunk-RVERAFFY.js +291 -0
- package/dist/chunk-RVERAFFY.js.map +1 -0
- package/dist/chunk-SLWCCHNP.js +491 -0
- package/dist/chunk-SLWCCHNP.js.map +1 -0
- package/dist/chunk-SMZGAPCU.js +70 -0
- package/dist/chunk-SMZGAPCU.js.map +1 -0
- package/dist/{chunk-7BR3DO2A.js → chunk-SRSJO5QE.js} +13 -4
- package/dist/chunk-SRSJO5QE.js.map +1 -0
- package/dist/chunk-TJK44RQC.js +452 -0
- package/dist/chunk-TJK44RQC.js.map +1 -0
- package/dist/chunk-UIDKY4UB.js +14 -0
- package/dist/chunk-UIDKY4UB.js.map +1 -0
- package/dist/chunk-V3DX3K65.js +43 -0
- package/dist/chunk-V3DX3K65.js.map +1 -0
- package/dist/chunk-VZOFZPSO.js +71 -0
- package/dist/chunk-VZOFZPSO.js.map +1 -0
- package/dist/chunk-W2AC6Q2A.js +83 -0
- package/dist/chunk-W2AC6Q2A.js.map +1 -0
- package/dist/chunk-WF75U3V4.js +33 -0
- package/dist/chunk-WF75U3V4.js.map +1 -0
- package/dist/chunk-WTSNV5RG.js +52 -0
- package/dist/chunk-WTSNV5RG.js.map +1 -0
- package/dist/{chunk-YGN7OMLT.js → chunk-X5QSICJ3.js} +8 -5
- package/dist/chunk-X5QSICJ3.js.map +1 -0
- package/dist/chunk-YDGNU2OD.js +61 -0
- package/dist/chunk-YDGNU2OD.js.map +1 -0
- package/dist/collapsible-panel.d.ts +133 -0
- package/dist/collapsible-panel.js +131 -0
- package/dist/collapsible-panel.js.map +1 -0
- package/dist/content-item.d.ts +81 -0
- package/dist/content-item.js +4 -0
- package/dist/content-item.js.map +1 -0
- package/dist/css-values.d.ts +107 -0
- package/dist/css-values.js +3 -0
- package/dist/css-values.js.map +1 -0
- package/dist/device-class.js +33 -5
- package/dist/device-class.js.map +1 -1
- package/dist/disclosure-arrow.d.ts +6 -1
- package/dist/disclosure-arrow.js +2 -1
- package/dist/empty-state.d.ts +5 -2
- package/dist/empty-state.js +2 -1
- package/dist/flex-alignment-4ms8ZbV8.d.ts +5 -0
- package/dist/floating-toolbar.d.ts +51 -0
- package/dist/floating-toolbar.js +3 -0
- package/dist/floating-toolbar.js.map +1 -0
- package/dist/grid.d.ts +50 -0
- package/dist/grid.js +5 -0
- package/dist/grid.js.map +1 -0
- package/dist/help-tags-register.d.ts +13 -0
- package/dist/help-tags-register.js +9 -0
- package/dist/help-tags-register.js.map +1 -0
- package/dist/index.d.ts +34 -14
- package/dist/index.js +43 -23
- package/dist/{menu-action-row.d.ts → list-action-row.d.ts} +21 -9
- package/dist/list-action-row.js +8 -0
- package/dist/list-action-row.js.map +1 -0
- package/dist/list-header.d.ts +99 -0
- package/dist/list-header.js +10 -0
- package/dist/list-header.js.map +1 -0
- package/dist/list-inset-control.d.ts +22 -0
- package/dist/list-inset-control.js +3 -0
- package/dist/list-inset-control.js.map +1 -0
- package/dist/list-inset-text.d.ts +30 -0
- package/dist/list-inset-text.js +4 -0
- package/dist/list-inset-text.js.map +1 -0
- package/dist/list-item.d.ts +62 -0
- package/dist/list-item.js +8 -0
- package/dist/list-item.js.map +1 -0
- package/dist/list.d.ts +63 -0
- package/dist/list.js +6 -0
- package/dist/list.js.map +1 -0
- package/dist/loading-spinner.d.ts +9 -2
- package/dist/loading-spinner.js +2 -1
- package/dist/lucide-icon.d.ts +9 -2
- package/dist/lucide-icon.js +2 -1
- package/dist/nav-stack.d.ts +66 -10
- package/dist/nav-stack.js +6 -1
- package/dist/pane.d.ts +108 -0
- package/dist/pane.js +4 -0
- package/dist/pane.js.map +1 -0
- package/dist/panel-toolbar-DDKh4uKd.d.ts +72 -0
- package/dist/popup-menu-register.d.ts +9 -0
- package/dist/popup-menu-register.js +244 -0
- package/dist/popup-menu-register.js.map +1 -0
- package/dist/popup-menu.d.ts +100 -0
- package/dist/popup-menu.js +3 -0
- package/dist/popup-menu.js.map +1 -0
- package/dist/resizable-region.d.ts +32 -3
- package/dist/resizable-region.js +1 -1
- package/dist/row.d.ts +48 -0
- package/dist/row.js +6 -0
- package/dist/row.js.map +1 -0
- package/dist/segmented-control.d.ts +12 -9
- package/dist/segmented-control.js +1 -2
- package/dist/select-register.d.ts +5 -1
- package/dist/select-register.js +243 -2
- package/dist/select-register.js.map +1 -1
- package/dist/select.d.ts +102 -8
- package/dist/select.js +3 -1
- package/dist/semantic-content-BbzjvSu9.d.ts +14 -0
- package/dist/sides-BPSWde0A.d.ts +4 -0
- package/dist/skeleton.d.ts +14 -7
- package/dist/skeleton.js +2 -1
- package/dist/spacer.d.ts +18 -0
- package/dist/spacer.js +4 -0
- package/dist/spacer.js.map +1 -0
- package/dist/split-view.d.ts +39 -7
- package/dist/split-view.js +120 -10
- package/dist/split-view.js.map +1 -1
- package/dist/state-banner.d.ts +18 -6
- package/dist/state-banner.js +3 -1
- package/dist/styles/app-tab.css +224 -0
- package/dist/styles/badge.css +118 -0
- package/dist/styles/catalog/components/catalog-example-stack.css +9 -0
- package/dist/styles/catalog/components/catalog-example.css +168 -0
- package/dist/styles/catalog/components/catalog-stage.css +55 -0
- package/dist/styles/catalog/components/catalog.css +12 -0
- package/dist/styles/catalog.css +5 -0
- package/dist/styles/chip.css +132 -0
- package/dist/styles/collapsible-panel.css +575 -0
- package/dist/styles/content-item.css +75 -0
- package/dist/styles/document.css +23 -0
- package/dist/styles/floating-toolbar.css +119 -0
- package/dist/styles/foundation.css +178 -40
- package/dist/styles/grid.css +35 -0
- package/dist/styles/layout.css +3 -41
- package/dist/styles/list-action-row.css +258 -0
- package/dist/styles/list-header.css +229 -0
- package/dist/styles/list-inset-control.css +30 -0
- package/dist/styles/list-inset-text.css +29 -0
- package/dist/styles/list-item.css +255 -0
- package/dist/styles/list.css +133 -0
- package/dist/styles/loading-spinner.css +4 -0
- package/dist/styles/lucide-icon.css +5 -0
- package/dist/styles/nav-stack.css +172 -47
- package/dist/styles/pane.css +263 -0
- package/dist/styles/popup-menu.css +285 -0
- package/dist/styles/resizable-region.css +438 -4
- package/dist/styles/row.css +111 -0
- package/dist/styles/segmented-control.css +36 -9
- package/dist/styles/select.css +337 -21
- package/dist/styles/skeleton.css +64 -3
- package/dist/styles/spacer.css +11 -0
- package/dist/styles/split-view.css +114 -3
- package/dist/styles/state-banner.css +94 -19
- package/dist/styles/styles.css +13 -4
- package/dist/styles/sunken-panel.css +29 -0
- package/dist/styles/surface-scaffold.css +56 -0
- package/dist/styles/tab-bar.css +147 -9
- package/dist/styles/tab-scaffold.css +126 -3
- package/dist/styles/text.css +112 -0
- package/dist/styles/token-search-field.css +155 -55
- package/dist/styles/toolbar-control-group.css +608 -29
- package/dist/styles/toolbar-text.css +101 -8
- package/dist/styles/toolbar.css +343 -12
- package/dist/styles/value-table.css +34 -4
- package/dist/styles/webawesome.css +365 -10
- package/dist/styles/workbench.css +980 -7
- package/dist/sunken-panel.d.ts +27 -0
- package/dist/sunken-panel.js +3 -0
- package/dist/sunken-panel.js.map +1 -0
- package/dist/surface-scaffold.d.ts +30 -0
- package/dist/surface-scaffold.js +3 -0
- package/dist/surface-scaffold.js.map +1 -0
- package/dist/tab-bar.d.ts +39 -6
- package/dist/tab-bar.js +1 -1
- package/dist/tab-scaffold.d.ts +57 -7
- package/dist/tab-scaffold.js +83 -9
- package/dist/tab-scaffold.js.map +1 -1
- package/dist/text.d.ts +56 -0
- package/dist/text.js +3 -0
- package/dist/text.js.map +1 -0
- package/dist/token-search-field-CVGwDn2y.d.ts +154 -0
- package/dist/token-search-field.d.ts +3 -43
- package/dist/token-search-field.js +2 -1
- package/dist/token-search-model.d.ts +3 -0
- package/dist/token-search-model.js +3 -0
- package/dist/token-search-model.js.map +1 -0
- package/dist/toolbar-control-group.d.ts +77 -7
- package/dist/toolbar-control-group.js +3 -1
- package/dist/toolbar-text.d.ts +35 -4
- package/dist/toolbar-text.js +2 -1
- package/dist/toolbar.d.ts +59 -7
- package/dist/toolbar.js +1 -1
- package/dist/types-BuHELdXR.d.ts +117 -0
- package/dist/value-table.d.ts +11 -3
- package/dist/value-table.js +2 -1
- package/dist/webawesome.d.ts +8 -1
- package/dist/wire-catalog.d.ts +82 -0
- package/dist/wire-catalog.js +327 -0
- package/dist/wire-catalog.js.map +1 -0
- package/dist/wire-content-items.d.ts +11 -0
- package/dist/wire-content-items.js +114 -0
- package/dist/wire-content-items.js.map +1 -0
- package/dist/wire-nav-stack.d.ts +4 -1
- package/dist/wire-nav-stack.js +147 -7
- package/dist/wire-nav-stack.js.map +1 -1
- package/dist/wire-resizable-regions.d.ts +2 -1
- package/dist/wire-resizable-regions.js +2 -1
- package/dist/wire-scroll-dividers.d.ts +72 -0
- package/dist/wire-scroll-dividers.js +3 -0
- package/dist/wire-scroll-dividers.js.map +1 -0
- package/dist/wire-sidebar.d.ts +63 -0
- package/dist/wire-sidebar.js +231 -0
- package/dist/wire-sidebar.js.map +1 -0
- package/dist/wire-tab-bars.d.ts +19 -2
- package/dist/wire-tab-bars.js +1 -1
- package/dist/wire-tab-scaffold.js +9 -4
- package/dist/wire-tab-scaffold.js.map +1 -1
- package/dist/wire-token-search-fields.d.ts +65 -3
- package/dist/wire-token-search-fields.js +2 -1
- package/dist/wire-workbench.d.ts +116 -0
- package/dist/wire-workbench.js +6 -0
- package/dist/wire-workbench.js.map +1 -0
- package/dist/workbench.d.ts +244 -9
- package/dist/workbench.js +17 -15
- package/dist/workbench.js.map +1 -1
- package/docs/accessibility.md +476 -24
- package/docs/app-layouts.md +106 -8
- package/docs/apple-hig.md +1 -0
- package/docs/catalog.md +483 -0
- package/docs/collapsible-panel.md +371 -0
- package/docs/component-contract.md +667 -56
- package/docs/component-integration.md +75 -0
- package/docs/component-selection.md +540 -95
- package/docs/design/templates/empty-state/filtered-dark.svg +1 -0
- package/docs/design/templates/empty-state/filtered.svg +1 -0
- package/docs/design/templates/empty-state/with-action-dark.svg +1 -0
- package/docs/design/templates/empty-state/with-action.svg +1 -0
- package/docs/design/templates/empty-state-dark.svg +1 -0
- package/docs/design/templates/empty-state.svg +1 -0
- package/docs/design/templates/list/compact-dark.svg +1 -0
- package/docs/design/templates/list/compact.svg +1 -0
- package/docs/design/templates/list/stack-dark.svg +1 -0
- package/docs/design/templates/list/stack.svg +1 -0
- package/docs/design/templates/list-action-row/default-dark.svg +1 -0
- package/docs/design/templates/list-action-row/default.svg +1 -0
- package/docs/design/templates/list-action-row/selected-dark.svg +1 -0
- package/docs/design/templates/list-action-row/selected.svg +1 -0
- package/docs/design/templates/list-action-row-dark.svg +1 -0
- package/docs/design/templates/list-action-row.svg +1 -0
- package/docs/design/templates/list-dark.svg +1 -0
- package/docs/design/templates/list-header/action-dark.svg +1 -0
- package/docs/design/templates/list-header/action.svg +1 -0
- package/docs/design/templates/list-header/count-dark.svg +1 -0
- package/docs/design/templates/list-header/count.svg +1 -0
- package/docs/design/templates/list-header/inline-action-dark.svg +1 -0
- package/docs/design/templates/list-header/inline-action.svg +1 -0
- package/docs/design/templates/list-header/toggle-collapsed-dark.svg +1 -0
- package/docs/design/templates/list-header/toggle-collapsed.svg +1 -0
- package/docs/design/templates/list-header/toggle-expanded-dark.svg +1 -0
- package/docs/design/templates/list-header/toggle-expanded.svg +1 -0
- package/docs/design/templates/list-header-dark.svg +1 -0
- package/docs/design/templates/list-header.svg +1 -0
- package/docs/design/templates/list-item/default-dark.svg +1 -0
- package/docs/design/templates/list-item/default.svg +1 -0
- package/docs/design/templates/list-item/multiline-dark.svg +1 -0
- package/docs/design/templates/list-item/multiline.svg +1 -0
- package/docs/design/templates/list-item/selected-dark.svg +1 -0
- package/docs/design/templates/list-item/selected.svg +1 -0
- package/docs/design/templates/list-item/trailing-dark.svg +1 -0
- package/docs/design/templates/list-item/trailing.svg +1 -0
- package/docs/design/templates/list-item-dark.svg +1 -0
- package/docs/design/templates/list-item.svg +1 -0
- package/docs/design/templates/list.svg +1 -0
- package/docs/design/templates/segmented-control/equal-dark.svg +1 -0
- package/docs/design/templates/segmented-control/equal.svg +1 -0
- package/docs/design/templates/segmented-control/pill-small-dark.svg +1 -0
- package/docs/design/templates/segmented-control/pill-small.svg +1 -0
- package/docs/design/templates/segmented-control-dark.svg +1 -0
- package/docs/design/templates/segmented-control.svg +1 -0
- package/docs/design/templates/skeleton/avatar-dark.svg +1 -0
- package/docs/design/templates/skeleton/avatar.svg +1 -0
- package/docs/design/templates/skeleton/block-dark.svg +1 -0
- package/docs/design/templates/skeleton/block.svg +1 -0
- package/docs/design/templates/skeleton/lines-dark.svg +1 -0
- package/docs/design/templates/skeleton/lines.svg +1 -0
- package/docs/design/templates/skeleton-dark.svg +1 -0
- package/docs/design/templates/skeleton.svg +1 -0
- package/docs/design/templates/state-banner/danger-dark.svg +1 -0
- package/docs/design/templates/state-banner/danger.svg +1 -0
- package/docs/design/templates/state-banner/info-badge-dark.svg +1 -0
- package/docs/design/templates/state-banner/info-badge.svg +1 -0
- package/docs/design/templates/state-banner/info-dark.svg +1 -0
- package/docs/design/templates/state-banner/info.svg +1 -0
- package/docs/design/templates/state-banner/success-dark.svg +1 -0
- package/docs/design/templates/state-banner/success.svg +1 -0
- package/docs/design/templates/state-banner/warning-dark.svg +1 -0
- package/docs/design/templates/state-banner/warning.svg +1 -0
- package/docs/design/templates/state-banner-dark.svg +1 -0
- package/docs/design/templates/state-banner.svg +1 -0
- package/docs/design/templates/tab-bar/workspace-tabs-dark.svg +1 -0
- package/docs/design/templates/tab-bar/workspace-tabs.svg +1 -0
- package/docs/design/templates/tab-bar-dark.svg +1 -0
- package/docs/design/templates/tab-bar.svg +1 -0
- package/docs/design/templates/token-search-field/empty-dark.svg +1 -0
- package/docs/design/templates/token-search-field/empty.svg +1 -0
- package/docs/design/templates/token-search-field/tokens-dark.svg +1 -0
- package/docs/design/templates/token-search-field/tokens.svg +1 -0
- package/docs/design/templates/token-search-field-dark.svg +1 -0
- package/docs/design/templates/token-search-field.svg +1 -0
- package/docs/design/templates/toolbar/title-and-actions-dark.svg +1 -0
- package/docs/design/templates/toolbar/title-and-actions.svg +1 -0
- package/docs/design/templates/toolbar-control-group/borderless-single-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/borderless-single.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-buttons-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/icon-buttons.svg +1 -0
- package/docs/design/templates/toolbar-control-group/push-buttons-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/push-buttons.svg +1 -0
- package/docs/design/templates/toolbar-control-group/with-text-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group/with-text.svg +1 -0
- package/docs/design/templates/toolbar-control-group-dark.svg +1 -0
- package/docs/design/templates/toolbar-control-group.svg +1 -0
- package/docs/design/templates/toolbar-dark.svg +1 -0
- package/docs/design/templates/toolbar-text/default-dark.svg +1 -0
- package/docs/design/templates/toolbar-text/default.svg +1 -0
- package/docs/design/templates/toolbar-text/large-dark.svg +1 -0
- package/docs/design/templates/toolbar-text/large.svg +1 -0
- package/docs/design/templates/toolbar-text/small-dark.svg +1 -0
- package/docs/design/templates/toolbar-text/small.svg +1 -0
- package/docs/design/templates/toolbar-text-dark.svg +1 -0
- package/docs/design/templates/toolbar-text.svg +1 -0
- package/docs/design/templates/toolbar.svg +1 -0
- package/docs/design/templates/value-table/details-dark.svg +1 -0
- package/docs/design/templates/value-table/details.svg +1 -0
- package/docs/design/templates/value-table-dark.svg +1 -0
- package/docs/design/templates/value-table.svg +1 -0
- package/docs/design/templates.md +138 -0
- package/docs/design-philosophy.md +48 -20
- package/docs/device-class.md +10 -9
- package/docs/document-baseline.md +38 -0
- package/docs/examples/application-ui-profile.json +60 -0
- package/docs/examples/command-palette-adapter.tsx +69 -17
- package/docs/examples/component-catalog-extension.json +52 -0
- package/docs/examples/component-composition-extension.json +80 -0
- package/docs/layout.md +534 -59
- package/docs/nav-stack.md +138 -14
- package/docs/recipes.md +137 -54
- package/docs/split-view.md +74 -6
- package/docs/sunken-panel.md +87 -0
- package/docs/surface-scaffold.md +28 -0
- package/docs/tab-scaffold.md +99 -10
- package/docs/type-contracts.md +134 -0
- package/docs/ui-analyzer.md +384 -0
- package/docs/ui-doctor.md +153 -0
- package/docs/ui-evaluator.md +139 -0
- package/docs/ux-demo.md +41 -16
- package/docs/webawesome-theme.md +218 -16
- package/docs/workbench.md +608 -12
- package/doctor/cli.mjs +120 -0
- package/doctor/config.schema.json +94 -0
- package/doctor/eslint-diagnostics.d.mts +24 -0
- package/doctor/eslint-diagnostics.mjs +24 -0
- package/doctor/index.d.mts +122 -0
- package/doctor/index.mjs +1364 -0
- package/doctor/report.schema.json +118 -0
- package/evaluator/cli.mjs +89 -0
- package/evaluator/index.d.mts +90 -0
- package/evaluator/index.mjs +1203 -0
- package/evaluator/report.schema.json +152 -0
- package/llms.txt +445 -48
- package/package.json +249 -37
- package/scripts/component-catalog-extension-validation.mjs +28 -0
- package/traversal-exclusions.mjs +27 -0
- package/ux-demo/recipes/app-shell.tsx +205 -17
- package/ux-demo/recipes/collapsible-sidebar.tsx +260 -0
- package/ux-demo/recipes/compact-toolbar.tsx +135 -14
- package/ux-demo/recipes/composer-form.tsx +133 -21
- package/ux-demo/recipes/list-detail-dialog.tsx +282 -0
- package/ux-demo/recipes/list-workspace-states.tsx +183 -11
- package/ux-demo/recipes/loaders.ts +15 -5
- package/ux-demo/recipes/loading-inspector.tsx +204 -0
- package/ux-demo/recipes/mount-recipe.ts +28 -10
- package/ux-demo/recipes/navigation-sidebar.tsx +139 -13
- package/ux-demo/recipes/navigation-stack.tsx +79 -24
- package/ux-demo/recipes/types.ts +23 -2
- package/ux-demo/recipes/workspace-header.tsx +86 -5
- package/dist/browser/menu-action-row.js +0 -4
- package/dist/browser/menu-item.js +0 -4
- package/dist/chunk-2RJBFNB6.js +0 -172
- package/dist/chunk-2RJBFNB6.js.map +0 -1
- package/dist/chunk-2Z5TWHKQ.js +0 -34
- package/dist/chunk-2Z5TWHKQ.js.map +0 -1
- package/dist/chunk-3YD6ZNRA.js +0 -34
- package/dist/chunk-3YD6ZNRA.js.map +0 -1
- package/dist/chunk-55DATV3A.js +0 -10
- package/dist/chunk-55DATV3A.js.map +0 -1
- package/dist/chunk-56RJXPHG.js.map +0 -1
- package/dist/chunk-7BR3DO2A.js.map +0 -1
- package/dist/chunk-7LDCTU7D.js +0 -134
- package/dist/chunk-7LDCTU7D.js.map +0 -1
- package/dist/chunk-AQNKJRFH.js +0 -79
- package/dist/chunk-AQNKJRFH.js.map +0 -1
- package/dist/chunk-B24C4A6T.js +0 -22
- package/dist/chunk-B24C4A6T.js.map +0 -1
- package/dist/chunk-DAFFDO33.js +0 -14
- package/dist/chunk-DAFFDO33.js.map +0 -1
- package/dist/chunk-H6URQGJI.js +0 -42
- package/dist/chunk-H6URQGJI.js.map +0 -1
- package/dist/chunk-I7INRJO4.js +0 -17
- package/dist/chunk-I7INRJO4.js.map +0 -1
- package/dist/chunk-KJYXOTG3.js +0 -10
- package/dist/chunk-KJYXOTG3.js.map +0 -1
- package/dist/chunk-LC3YSAZC.js +0 -39
- package/dist/chunk-LC3YSAZC.js.map +0 -1
- package/dist/chunk-M5JRI6MN.js +0 -27
- package/dist/chunk-M5JRI6MN.js.map +0 -1
- package/dist/chunk-MBR3TYNL.js +0 -14
- package/dist/chunk-MBR3TYNL.js.map +0 -1
- package/dist/chunk-MFKMHK5W.js +0 -10
- package/dist/chunk-MFKMHK5W.js.map +0 -1
- package/dist/chunk-MQIZAD4A.js +0 -227
- package/dist/chunk-MQIZAD4A.js.map +0 -1
- package/dist/chunk-N3SUXLLS.js +0 -23
- package/dist/chunk-N3SUXLLS.js.map +0 -1
- package/dist/chunk-PONXLV5U.js +0 -19
- package/dist/chunk-PONXLV5U.js.map +0 -1
- package/dist/chunk-TZNCCM5H.js +0 -20
- package/dist/chunk-TZNCCM5H.js.map +0 -1
- package/dist/chunk-W2FK7ZNY.js +0 -26
- package/dist/chunk-W2FK7ZNY.js.map +0 -1
- package/dist/chunk-WVPYVDXY.js +0 -15
- package/dist/chunk-WVPYVDXY.js.map +0 -1
- package/dist/chunk-YGN7OMLT.js.map +0 -1
- package/dist/chunk-YKUUZPED.js +0 -24
- package/dist/chunk-YKUUZPED.js.map +0 -1
- package/dist/menu-action-row.js +0 -5
- package/dist/menu-action-row.js.map +0 -1
- package/dist/menu-header.d.ts +0 -43
- package/dist/menu-header.js +0 -7
- package/dist/menu-header.js.map +0 -1
- package/dist/menu-item.d.ts +0 -33
- package/dist/menu-item.js +0 -5
- package/dist/menu-item.js.map +0 -1
- package/dist/panel-header.d.ts +0 -28
- package/dist/panel-header.js +0 -7
- package/dist/panel-header.js.map +0 -1
- package/dist/styles/menu-action-row.css +0 -160
- package/dist/styles/menu-header.css +0 -160
- package/dist/styles/menu-item.css +0 -123
- package/dist/styles/panel-header.css +0 -64
- package/ux-demo/recipes/master-detail-dialog.tsx +0 -45
- package/ux-demo/recipes/recipes.css +0 -355
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# Static CSS and layout ownership analyzer
|
|
2
|
+
|
|
3
|
+
`kerf-ui-analyze` checks integration facts that TypeScript and an individual
|
|
4
|
+
ESLint file cannot see across component markup, stylesheets, the component
|
|
5
|
+
catalog, and the application UI profile.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx kerf-ui-analyze --root . --format text src
|
|
9
|
+
npx kerf-ui-analyze --root . --format json --output artifacts/kerf-ui.json
|
|
10
|
+
npx kerf-ui-analyze --root . --format sarif --output artifacts/kerf-ui.sarif
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The analyzer is opt-in. Add a project script such as
|
|
14
|
+
`"check:kerf-ui-layout": "kerf-ui-analyze --root . src"`; it never executes
|
|
15
|
+
application code. It discovers `.kerf-ui-profile.json`, joins every declared
|
|
16
|
+
composition catalog by `package:id`, parses literal JavaScript/TypeScript
|
|
17
|
+
component calls, TSX/JSX, and CSS, and reports portable repository-relative
|
|
18
|
+
locations.
|
|
19
|
+
|
|
20
|
+
An imported component name is resolved to a catalog entry by `package:id`, not
|
|
21
|
+
by display name: several entries share one (the Kerf `Select` and the Web
|
|
22
|
+
Awesome `wa-select` are both "Select"), so the analyzer joins each composition
|
|
23
|
+
entry to its selection-catalog public exports and import subpaths and picks
|
|
24
|
+
the entry that the import actually names. That keeps the Kerf component's
|
|
25
|
+
CSS-value contracts (`KUI-L013`–`KUI-L017`) in force for `Select` and
|
|
26
|
+
`Skeleton`.
|
|
27
|
+
|
|
28
|
+
Only component exports resolve to an entry. A selection-catalog
|
|
29
|
+
`publicExports` list also carries the helpers a component's props consume
|
|
30
|
+
(`Select` ships `uiColor`, `List` ships `px`, `rem`, and `flex`), and a helper
|
|
31
|
+
never inherits its entry's contracts, so `uiColor({ choices: [...] })` is not
|
|
32
|
+
checked as a `Select` call. A component export is one whose name starts with
|
|
33
|
+
an uppercase letter, the same rule JSX uses to tell a component tag from an
|
|
34
|
+
intrinsic element; `eslint-plugin-kerfjs` applies the identical rule, and the
|
|
35
|
+
package's catalog check fails if a cataloged render function (a value
|
|
36
|
+
returning `SafeHtml`) is lowercase or any other runtime value is uppercase.
|
|
37
|
+
Third-party catalogs should follow the same naming convention.
|
|
38
|
+
|
|
39
|
+
Analysis is scoped per source file. Each TSX/JSX file resolves package,
|
|
40
|
+
workspace, and parent-to-child directory profiles from its own location, then
|
|
41
|
+
follows its relative CSS imports (including nested CSS `@import`s). Stylesheet
|
|
42
|
+
facts are never pooled across unrelated files or sibling packages, and a shared
|
|
43
|
+
stylesheet's diagnostics are evaluated against every importing source profile.
|
|
44
|
+
A violation stays active when any consumer has not narrowly excepted it;
|
|
45
|
+
duplicate source findings collapse to one result. Directly targeted or orphaned
|
|
46
|
+
CSS uses its own directory profile. A direct changed-file target still brings
|
|
47
|
+
its reachable project-local styles into the report, including quoted or
|
|
48
|
+
unquoted `url()` imports; external package styles remain outside the consumer
|
|
49
|
+
boundary.
|
|
50
|
+
|
|
51
|
+
Recursive discovery excludes generated and tool-owned trees, including any
|
|
52
|
+
nested `.claude/worktrees` checkout. Those checkouts are separate repositories,
|
|
53
|
+
not application source, and cannot contribute files, diagnostics, profile
|
|
54
|
+
policy, or analysis inputs to the containing application.
|
|
55
|
+
|
|
56
|
+
## Diagnostics and exit behavior
|
|
57
|
+
|
|
58
|
+
| Rule | Kind | Meaning |
|
|
59
|
+
| ---------- | ------ | ------------------------------------------------------------------------------------- |
|
|
60
|
+
| `KUI-L001` | error | A stylesheet reaches into a private or unknown `.kui-*` selector. |
|
|
61
|
+
| `KUI-L002` | error | A stylesheet references an unknown or private `--kui-*` token. |
|
|
62
|
+
| `KUI-L003` | error | One element combines public classes that claim the same geometry. |
|
|
63
|
+
| `KUI-L004` | review | Nested literal classes both add a content inset. |
|
|
64
|
+
| `KUI-L005` | — | Retired; folded into `KUI-L019` (see below). |
|
|
65
|
+
| `KUI-L006` | review | Literal spacing falls outside Kerf's approved scale. |
|
|
66
|
+
| `KUI-L007` | error | A declared scroll owner is nested inside another scroll owner. |
|
|
67
|
+
| `KUI-L008` | review | A dynamic class expression cannot be classified soundly. |
|
|
68
|
+
| `KUI-L009` | error | A stylesheet cannot be parsed. |
|
|
69
|
+
| `KUI-L010` | error | A selector reaches a private Kerf descendant from a public root. |
|
|
70
|
+
| `KUI-L011` | error | A `::part()` target is not cataloged as a public extension point. |
|
|
71
|
+
| `KUI-L012` | error | CSS assigns a Kerf token without a public configuration contract. |
|
|
72
|
+
| `KUI-L013` | error | A CSS-adjacent prop uses an unknown shorthand or raw literal. |
|
|
73
|
+
| `KUI-L014` | error | A typed helper produces the wrong property grammar. |
|
|
74
|
+
| `KUI-L015` | error | An expression-only helper is passed without a composer. |
|
|
75
|
+
| `KUI-L016` | error | A removed declaration-list escape hatch is used. |
|
|
76
|
+
| `KUI-L017` | review | A valid but exceptional off-scale shorthand needs justification. |
|
|
77
|
+
| `KUI-L018` | review | A loud fill override lacks a governing `on-loud`, or the literal pair is below 4.5:1. |
|
|
78
|
+
| `KUI-L019` | error | A rule's subject is another package's component (class, `data-component`, or tag). |
|
|
79
|
+
| `KUI-L020` | error | CSS reads or writes another component's private `--_*` variable. |
|
|
80
|
+
| `KUI-L021` | error | CSS overrides a component token that a typed prop sets. |
|
|
81
|
+
| `KUI-L022` | error | A class placed on a component's root is styled by the application (a hook class). |
|
|
82
|
+
| `KUI-L023` | error | A source module renders another component's public class as its own markup. |
|
|
83
|
+
|
|
84
|
+
For `KUI-L004` and `KUI-L007`, scroll and inset declarations belong to the
|
|
85
|
+
subject of each CSS selector. An ancestor class used only to qualify a
|
|
86
|
+
descendant selector is not treated as a scroll owner or inset source.
|
|
87
|
+
|
|
88
|
+
Errors are provable contract violations and make the command exit 1. Review
|
|
89
|
+
findings are deliberately heuristic and do not fail by default; pass
|
|
90
|
+
`--fail-on-review` when a project has reviewed its baseline and wants them to
|
|
91
|
+
gate CI. Text, versioned JSON, and SARIF carry the same stable rule ids,
|
|
92
|
+
source locations, evidence, and ownership chain.
|
|
93
|
+
|
|
94
|
+
For staged adoption, pass `--adoption`. Ownership-boundary diagnostics are
|
|
95
|
+
reported as review findings so a team can inventory and migrate its baseline;
|
|
96
|
+
combine it with the exact file-and-rule exceptions below for intentional legacy
|
|
97
|
+
integrations. Remove `--adoption` to make unsupported overrides blocking.
|
|
98
|
+
|
|
99
|
+
Catalog `boundaries.publicClasses` and `boundaries.publicTokens` are explicit
|
|
100
|
+
CSS extension points. A component catalog may additionally list shadow-part
|
|
101
|
+
names in `boundaries.publicParts`; all other `::part()` targets are private.
|
|
102
|
+
Part names are scoped to that entry's `boundaries.rootClass`, so an extension
|
|
103
|
+
point on one component does not authorize the same-named part on another.
|
|
104
|
+
Selectors for application-owned content remain allowed, including in a
|
|
105
|
+
component's context (`.kui-pane > .my-content`): a public class is an
|
|
106
|
+
extension point for naming a context, not for restyling the component itself,
|
|
107
|
+
which `KUI-L019` reports.
|
|
108
|
+
|
|
109
|
+
Catalog entries may also publish `cssValueProps`. Each path names its grammar,
|
|
110
|
+
finite shorthands, canonical and exceptional scale steps, accepted typed
|
|
111
|
+
helpers, expression-only helpers, raw escape policy, and examples. The analyzer
|
|
112
|
+
uses that same metadata for direct component calls and JSX, including nested
|
|
113
|
+
paths such as `choices[].color`; consumer catalogs receive identical checks.
|
|
114
|
+
Dynamic values remain a type-system responsibility rather than being guessed.
|
|
115
|
+
|
|
116
|
+
## Component ownership (`KUI-L019`–`KUI-L023`)
|
|
117
|
+
|
|
118
|
+
Components own their styles and are configured, never overridden. These
|
|
119
|
+
diagnostics apply the package's own `check:css-ownership` rules to
|
|
120
|
+
applications and component packages; the selector parsing is the same module
|
|
121
|
+
(`analyzer/selectors.mjs`), so the package rule and the consumer rule agree on
|
|
122
|
+
which element a selector styles.
|
|
123
|
+
|
|
124
|
+
- **`KUI-L019` — a rule restyles a cataloged component.** The rule's subject
|
|
125
|
+
(the rightmost compound of each selector, ignoring `:has()` and `:not()`
|
|
126
|
+
arguments) names a public class of a cataloged component (`.kui-toolbar`,
|
|
127
|
+
`.kui-list-item__label`, `.acme-meter`), its `[data-component="…"]` root, or
|
|
128
|
+
a Web Awesome tag the package themes (`wa-button`, `wa-select`), and the rule
|
|
129
|
+
sets at least one ordinary property. It also catches a raw descendant of
|
|
130
|
+
cataloged component anatomy, such as `.kui-app-tab__select > svg`, even when
|
|
131
|
+
the component class is not the final compound. A component root used only as
|
|
132
|
+
context (`.kui-toolbar > .my-widget`) and app-named descendants remain
|
|
133
|
+
available for app-owned content. In component mode, classless element subjects
|
|
134
|
+
are also reported below a hook class placed on an imported component, below
|
|
135
|
+
another entry's root in the same package, or at a direct child root whose
|
|
136
|
+
JSX parent composes only a component with that cataloged `rootElement` type.
|
|
137
|
+
Descendant selectors also see a composed child's cataloged root and
|
|
138
|
+
`descendantElements` below an own wrapper. `LucideIcon` publishes `svg` and
|
|
139
|
+
its Lucide SVG child tags, so `.wrapper > svg`, `.wrapper svg`, and
|
|
140
|
+
`.wrapper path` can name the composed icon. Ambiguous evidence from multiple
|
|
141
|
+
different child components is discarded. For arbitrary custom `IconNode`
|
|
142
|
+
child tags outside the cataloged SVG tag list, a selector rooted at
|
|
143
|
+
`svg[data-lucide]` identifies the descendant as
|
|
144
|
+
LucideIcon-owned (for example `svg[data-lucide] filter`). Without that root
|
|
145
|
+
marker, an unknown tag beneath a wrapper remains ambiguous and is not
|
|
146
|
+
attributed to LucideIcon or to unrelated HTML.
|
|
147
|
+
The scan follows returned JSX from
|
|
148
|
+
same-module function and arrow-function components at their call sites,
|
|
149
|
+
recursively with a cycle guard; their intrinsic elements remain app-owned.
|
|
150
|
+
The finding names the composing
|
|
151
|
+
class and component. A `::part()` subject is left to
|
|
152
|
+
`KUI-L011`; a rule that only sets custom properties is configuration, judged
|
|
153
|
+
by the token rules. Forcing a component's dimensions is one such restyle
|
|
154
|
+
and reports here once: `.kui-pane { width: 300px }`.
|
|
155
|
+
- **`KUI-L020` — another component's private variable.** CSS reads or writes a
|
|
156
|
+
`--_<root>-*` variable of a cataloged component (`--_kui-list-gap`), or any
|
|
157
|
+
`--_kui-*` variable.
|
|
158
|
+
- **`KUI-L021` — a token a typed prop sets.** CSS assigns `--<root>-<prop>`
|
|
159
|
+
where the component has that typed prop (`--kui-list-gap` is `List gap`,
|
|
160
|
+
`--kui-floating-toolbar-inset` is `FloatingToolbar inset`). The message
|
|
161
|
+
quotes the prop's cataloged example.
|
|
162
|
+
- **`KUI-L022` — a hook class on a component's root.** A literal class on an
|
|
163
|
+
imported component's JSX (`<Toolbar className="header" />`) is the subject
|
|
164
|
+
of a rule in a stylesheet that file imports, so the application restyles the
|
|
165
|
+
component through it. The evidence names the stylesheet, line, and selector.
|
|
166
|
+
For cataloged application components, the import may be direct and relative
|
|
167
|
+
or use an exact or wildcard TypeScript `paths` alias from the nearest
|
|
168
|
+
`tsconfig.json` inside the analyzed root (including inherited options).
|
|
169
|
+
Unresolved aliases remain unclassified; the analyzer does not execute code.
|
|
170
|
+
- **`KUI-L023` — borrowed component markup.** An intrinsic JSX element, raw
|
|
171
|
+
HTML class attribute, `classList.add`/`toggle`/`replace`, `className`
|
|
172
|
+
assignment, or `setAttribute('class', …)` writes another cataloged
|
|
173
|
+
component's public class. The package boundary applies by default;
|
|
174
|
+
component mode also protects sibling modules through `componentSource`.
|
|
175
|
+
A component's own source and its `placeableClasses` remain allowed. Findings
|
|
176
|
+
name the class, owner, write site, and configuration route. This diagnostic
|
|
177
|
+
scans raw HTML in plain strings and the static segments of interpolated
|
|
178
|
+
template literals. When a substitution interrupts a class value, only
|
|
179
|
+
complete literal tokens are reported; split tokens are not guessed. It
|
|
180
|
+
skips test modules (`*.test.*`, `*.spec.*`, and files under `__tests__/`),
|
|
181
|
+
where HTML literals commonly assert on output rather than render markup.
|
|
182
|
+
|
|
183
|
+
Every message names the configuration to use (the component's typed props,
|
|
184
|
+
public tokens, and variants from its catalog entry) and ends with the same
|
|
185
|
+
route for a real need that has none: report the component gap to the owning
|
|
186
|
+
package (open a feature request) instead of overriding it.
|
|
187
|
+
|
|
188
|
+
What stays allowed mirrors the package rule, where a child styles itself in a
|
|
189
|
+
parent's context:
|
|
190
|
+
|
|
191
|
+
```css
|
|
192
|
+
/* Your own element in a component's context. */
|
|
193
|
+
.kui-toolbar > .my-widget {
|
|
194
|
+
margin-inline-start: auto;
|
|
195
|
+
}
|
|
196
|
+
/* Keying your own element on a component's state. */
|
|
197
|
+
.shell:has(.kui-select[open]) .my-status {
|
|
198
|
+
opacity: 0.5;
|
|
199
|
+
}
|
|
200
|
+
/* Configuring a component through a public token with no typed prop. */
|
|
201
|
+
.kui-list {
|
|
202
|
+
--kui-list-divider-color: var(--kui-color-neutral-border-quiet);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* KUI-L019, KUI-L020, KUI-L021. */
|
|
206
|
+
.kui-toolbar {
|
|
207
|
+
padding: 0;
|
|
208
|
+
}
|
|
209
|
+
.app {
|
|
210
|
+
--_kui-list-gap: 2px;
|
|
211
|
+
--kui-list-gap: 4px;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Ownership is decided per package: a stylesheet belongs to the package whose
|
|
216
|
+
`package.json` is nearest it, and only other packages' catalog entries are
|
|
217
|
+
foreign to it. A component package built on Kerf UI therefore styles its own
|
|
218
|
+
cataloged components (including `.kui-toolbar > .acme-meter` from its own
|
|
219
|
+
stylesheet) without findings, while an application that restyles
|
|
220
|
+
`.acme-meter` is reported against `@acme/widgets`. All four ids are
|
|
221
|
+
ownership-boundary diagnostics, so `--adoption` reports them as review
|
|
222
|
+
findings. `eslint-plugin-kerfjs`'s `ui-component-ownership` rule reports
|
|
223
|
+
`KUI-L020` and `KUI-L021` in JavaScript and TypeScript (style strings, style
|
|
224
|
+
objects, and `style.setProperty()`), where this analyzer reads only CSS.
|
|
225
|
+
|
|
226
|
+
Pass `ownership: 'component'` to `analyzeUiProject` to enforce the boundary
|
|
227
|
+
between components in one package. Selection entries with `source` participate
|
|
228
|
+
even without a composition entry. Each entry owns its declared `styleSources`
|
|
229
|
+
for CSS and its selection `source` module for JSX; when `styleSources` is absent,
|
|
230
|
+
direct relative CSS imports from that source are used. The analyzer also derives
|
|
231
|
+
literal JSX `class` and `className` values whose BEM block is styled in those
|
|
232
|
+
stylesheets. A root or styled literal establishes ownership of the whole BEM
|
|
233
|
+
block: `block`, `block__*`, and `block--*`, including dynamically constructed
|
|
234
|
+
element and modifier names. The boundary applies to selectors and markup class
|
|
235
|
+
writes, including a class prop on an imported component. Modules that share a
|
|
236
|
+
stylesheet co-own its classes, whether the stylesheet was declared or directly
|
|
237
|
+
imported; a source that imports none of an owner's stylesheets remains foreign.
|
|
238
|
+
When several entries infer the same class from a shared stylesheet, the
|
|
239
|
+
analyzer does not arbitrarily assign that class to one of them for `KUI-L019`.
|
|
240
|
+
Other sibling files are foreign.
|
|
241
|
+
Paths are relative to the entry's package root. Set
|
|
242
|
+
`implicitComponentOwnership: true` to apply the same source and stylesheet
|
|
243
|
+
inference to modules with direct CSS imports that have no catalog entry.
|
|
244
|
+
For a shell stylesheet imported once but used by style-less modules, pass
|
|
245
|
+
`ownershipGroups: [{ styleSources: ['src/style.css'], sources: ['src/main.tsx', 'src/app/'] }]`.
|
|
246
|
+
The paths are relative to the analyzed package root. A source ending in `/`
|
|
247
|
+
matches every module below that directory; other source paths match exactly.
|
|
248
|
+
Each group owns the subject classes in its stylesheets for `KUI-L019` and
|
|
249
|
+
`KUI-L023`. Its stylesheet and listed source modules share one owner, while
|
|
250
|
+
sibling stylesheets and modules remain foreign. This option applies only with
|
|
251
|
+
`ownership: 'component'`.
|
|
252
|
+
Set `ownershipContext: 'any'` alongside component ownership when one sibling
|
|
253
|
+
component must not mention another sibling's class anywhere in a CSS selector.
|
|
254
|
+
The default, `'subject'`, checks the styled subject. Strict findings use
|
|
255
|
+
`KUI-L019` with `evidence.via: 'context'` and a `position` of `ancestor`,
|
|
256
|
+
`sibling`, `has`, `is`, `where`, or `not`. Set
|
|
257
|
+
`ownershipContext: 'any-package'` to apply the same context rule to components
|
|
258
|
+
from other packages, including their public classes and `[data-component]`
|
|
259
|
+
roots. The `'any'` mode continues to allow cross-package context.
|
|
260
|
+
|
|
261
|
+
### Retired: `KUI-L005` (forced component dimension)
|
|
262
|
+
|
|
263
|
+
`KUI-L005` used to report, as a review finding, any `width`/`height`/`min-*`/
|
|
264
|
+
`max-*` declaration in a rule that mentioned a public component class
|
|
265
|
+
anywhere in its selector. `KUI-L019` now reports every restyle whose subject is
|
|
266
|
+
a cataloged component — dimensions included — so the subject case was
|
|
267
|
+
double-reported (an error and a review finding for one declaration). Its only
|
|
268
|
+
other shape, a public class as an ancestor
|
|
269
|
+
(`.kui-pane .app-sidebar { width: 240px }`), sizes the application's own
|
|
270
|
+
element in the component's context, which the ownership rules explicitly
|
|
271
|
+
allow. With no unique true positive left,
|
|
272
|
+
the rule was retired rather than narrowed. The analyzer no longer emits it;
|
|
273
|
+
the id stays in `ai/application-ui-diagnostic-ids-v1.json` (listed under
|
|
274
|
+
`retired`) so a profile exception that already names it still loads. Remove
|
|
275
|
+
such exceptions at your convenience.
|
|
276
|
+
|
|
277
|
+
## Loud fill / on-loud pairing (`KUI-L018`)
|
|
278
|
+
|
|
279
|
+
The shipped Web Awesome theme pairs every `--wa-color-{tone}-fill-loud` with a
|
|
280
|
+
`--wa-color-{tone}-on-loud` foreground that clears WCAG AA (4.5:1), for the
|
|
281
|
+
`neutral`, `brand`, `success`, `warning`, `danger`, and `pop` tones. A
|
|
282
|
+
stylesheet that overrides a loud fill without also setting that tone's
|
|
283
|
+
`on-loud` keeps the theme's foreground on an unknown fill, so the guarantee no
|
|
284
|
+
longer holds. `KUI-L018` reports such a fill and names each scope that lacks a
|
|
285
|
+
pair, and it reports a pair that is present but measurably fails.
|
|
286
|
+
|
|
287
|
+
**Which `on-loud` governs a fill.** Each individual selector of the fill
|
|
288
|
+
(after resolving CSS nesting and splitting selector lists) inside its chain of
|
|
289
|
+
at-rules is one scope. An `on-loud` of the same tone governs it when declared:
|
|
290
|
+
|
|
291
|
+
- in the same scope, in the same or a separate rule, in either order; or
|
|
292
|
+
- in an ancestor scope: a document-root selector (`:root`, `html`, `:host`,
|
|
293
|
+
`*`), or the leading part of a descendant or child chain (`.shell` governs
|
|
294
|
+
`.shell .panel` and `.frame > .tile`, but not `.shell-alt .panel` or
|
|
295
|
+
`.shell.dense`); and
|
|
296
|
+
- under no at-rule, or the same at-rule chain (an unconditional `on-loud`
|
|
297
|
+
applies inside `@media`, but one under `@media` or `@supports` is not proven
|
|
298
|
+
to apply to an unconditional fill).
|
|
299
|
+
|
|
300
|
+
That `on-loud` may live in the same stylesheet or in another one the same
|
|
301
|
+
entry loads: a source module that imports both stylesheets, or a stylesheet
|
|
302
|
+
that reaches both through `@import`. A stylesheet no entry loads with the fill
|
|
303
|
+
does not count. When several candidates apply, the same scope wins (this
|
|
304
|
+
stylesheet first), then the nearest ancestor.
|
|
305
|
+
|
|
306
|
+
**Contrast, when it can be proven.** When the fill and its governing `on-loud`
|
|
307
|
+
are both literal opaque colors (hex, `rgb()`/`rgba()`, `hsl()`/`hsla()`,
|
|
308
|
+
`white`/`black`, or a `light-dark()` of two of them), the analyzer computes the
|
|
309
|
+
WCAG contrast ratio in each color scheme and reports a pair below 4.5:1, with
|
|
310
|
+
the ratio and scheme in its evidence. An unpaired literal fill is accepted when
|
|
311
|
+
it clears 4.5:1 in both schemes against the theme's default `on-loud` for its
|
|
312
|
+
tone (`light-dark(#fff, #111113)` for `brand` and `danger`, `#1d1d1f` for
|
|
313
|
+
`success` and `warning`); `neutral` follows a surface token and `pop` has no
|
|
314
|
+
Web Awesome default, so their unpaired fills are always reported. Values it
|
|
315
|
+
cannot prove (`var()`, `color-mix()`, translucent colors, keywords) are not
|
|
316
|
+
measured: a present pair is accepted, and an unpaired fill is reported.
|
|
317
|
+
|
|
318
|
+
```css
|
|
319
|
+
/* KUI-L018: brand fill moves, its foreground does not. */
|
|
320
|
+
.billing {
|
|
321
|
+
--wa-color-brand-fill-loud: #7540a8;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* Paired: both in the same scope. */
|
|
325
|
+
.billing {
|
|
326
|
+
--wa-color-brand-fill-loud: #7540a8;
|
|
327
|
+
--wa-color-brand-on-loud: #ffffff;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/* Paired through an ancestor scope (6.87:1). */
|
|
331
|
+
:root {
|
|
332
|
+
--wa-color-brand-on-loud: #ffffff;
|
|
333
|
+
}
|
|
334
|
+
.billing {
|
|
335
|
+
--wa-color-brand-fill-loud: #7540a8;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* KUI-L018: present, but white on this fill measures 2.14:1. */
|
|
339
|
+
.billing {
|
|
340
|
+
--wa-color-brand-fill-loud: #7fb3ff;
|
|
341
|
+
--wa-color-brand-on-loud: #ffffff;
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
It is a review finding rather than an error because the analyzer cannot see
|
|
346
|
+
every cascade: an `on-loud` set inline, by script, or by a stylesheet it does
|
|
347
|
+
not analyze may still govern the fill. Set the `on-loud` explicitly beside the
|
|
348
|
+
fill, which also documents the pair; use `--fail-on-review` to gate it in CI.
|
|
349
|
+
See [the Web Awesome theme customization notes](./webawesome-theme.md#customization).
|
|
350
|
+
|
|
351
|
+
The JSON report schema is exported as
|
|
352
|
+
`@kerfjs/ui/analyzer/report.schema.json`.
|
|
353
|
+
|
|
354
|
+
Profile discovery, parsing, catalog-loading, and validation diagnostics retain
|
|
355
|
+
their stable `KUI-P###` ids in every output format and count as errors. The
|
|
356
|
+
analyzer therefore cannot silently pass with stale or unreadable policy input.
|
|
357
|
+
|
|
358
|
+
The analyzer is conservative about dynamic class expressions: it emits one
|
|
359
|
+
review finding and does not guess which selectors, tokens, or owners the value
|
|
360
|
+
might contain. CSS values expressed through public variables or `calc()` are
|
|
361
|
+
also left to their owning token contract rather than reverse-engineered.
|
|
362
|
+
|
|
363
|
+
## Narrow exceptions
|
|
364
|
+
|
|
365
|
+
Use the application profile's `exceptions` only for an exact rule and exact
|
|
366
|
+
repository-relative file. Every exception requires an id and rationale; broad
|
|
367
|
+
directories, globs, absolute paths, and traversal are rejected by the profile
|
|
368
|
+
validator.
|
|
369
|
+
|
|
370
|
+
```json
|
|
371
|
+
{
|
|
372
|
+
"id": "legacy-inspector-spacing",
|
|
373
|
+
"rules": ["KUI-L006"],
|
|
374
|
+
"target": "src/legacy/inspector.css",
|
|
375
|
+
"rationale": "The legacy inspector retains its measured spacing until migration."
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Review findings remain visible until explicitly suppressed. Avoid suppressing
|
|
380
|
+
`KUI-L001`, `KUI-L002`, `KUI-L003`, `KUI-L007`, `KUI-L009`, `KUI-L010`,
|
|
381
|
+
`KUI-L011`, `KUI-L012`, `KUI-L013`, `KUI-L014`, `KUI-L015`, `KUI-L016`,
|
|
382
|
+
`KUI-L019`, `KUI-L020`, `KUI-L021`, `KUI-L022`, or `KUI-L023`:
|
|
383
|
+
those indicate a
|
|
384
|
+
definite boundary or parsing failure rather than an aesthetic judgment.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Kerf UI doctor
|
|
2
|
+
|
|
3
|
+
`kerf-ui-doctor` is the supported repair-loop entry point for a Kerf application. It produces one versioned report from application-profile and catalog validation, TypeScript, the Kerf UI ESLint preset, static layout analysis, and an explicitly enabled browser evaluation.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npx kerf-ui-doctor --full
|
|
7
|
+
npx kerf-ui-doctor --changed
|
|
8
|
+
npx kerf-ui-doctor --package @acme/admin --changed
|
|
9
|
+
npx kerf-ui-doctor --full --browser-url http://127.0.0.1:4173
|
|
10
|
+
npx kerf-ui-doctor --full --fail-on warning
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The default terminal output is short and repair-oriented. `--format json` emits the schema-version-1 report; `--output report.json` writes the same representation. The package exports its contract from `@kerfjs/ui/doctor`, its configuration schema from `@kerfjs/ui/doctor/config.schema.json`, and its report schema from `@kerfjs/ui/doctor/report.schema.json`.
|
|
14
|
+
|
|
15
|
+
## Stages and trust boundary
|
|
16
|
+
|
|
17
|
+
The catalog stage discovers the package-default application UI profile and workspace/root-to-leaf directory layers. It validates referenced selection/composition catalogs and, when a workspace declares `package.json#kerfComponentCatalog`, invokes the installed `create-kerf-component` catalog checker to validate metadata, source exports, schema conformance, and generated-output drift. This checker reads source text; it does not import application modules.
|
|
18
|
+
|
|
19
|
+
TypeScript uses the compiler API with `noEmit`. ESLint loads the installed `eslint-plugin-kerfjs` `recommended-ui` preset (`--eslint strict-ui` opts into advisory rules as errors). This is deliberately an isolated Kerf lint pass rather than the consumer's complete ESLint configuration. For each file, the doctor projects the applicable core ESLint rules and `linterOptions` from the consumer configuration so core-rule suppression directives retain the same used/unused semantics as the application's normal lint command. Consumer plugin rules are not executed, and inline directives for plugins such as `@typescript-eslint` therefore do not produce false "rule definition not found" diagnostics; the consumer's normal ESLint command remains authoritative for those rules. Unknown `kerfjs/*` directives still fail the doctor pass. Stable KUI identifiers come from the packaged `application-ui-diagnostic-ids-v1.json` registry; the doctor also reads installed ESLint message metadata for semantic conflict detection instead of duplicating rule definitions. The analyzer calls the public `@kerfjs/ui/analyzer` contract. None of these stages executes generated application code.
|
|
20
|
+
|
|
21
|
+
Property-specific CSS values are evaluated from each first- or third-party
|
|
22
|
+
catalog entry's `cssValueProps`. `KUI-L013`–`KUI-L016` are blocking grammar
|
|
23
|
+
violations and name a preferred shorthand/helper; `KUI-L017` keeps an
|
|
24
|
+
exceptional but valid spacing choice in the review queue. The ESLint and
|
|
25
|
+
analyzer stages share these ids, so the normalized report can be consumed as
|
|
26
|
+
one repair loop without tool-specific translations.
|
|
27
|
+
|
|
28
|
+
The analyzer stage also reads consumer stylesheets for theme overrides:
|
|
29
|
+
`KUI-L018` is a review finding for a `--wa-color-{tone}-fill-loud` override
|
|
30
|
+
that lacks a governing `--wa-color-{tone}-on-loud` (in the same or an ancestor
|
|
31
|
+
scope, in any stylesheet the same entry loads), or whose literal pair measures
|
|
32
|
+
below 4.5:1 (see [the analyzer rule](./ui-analyzer.md#loud-fill--on-loud-pairing-kui-l018)).
|
|
33
|
+
|
|
34
|
+
Component ownership is part of the same loop. The analyzer stage reports
|
|
35
|
+
application or third-party CSS that restyles another package's component
|
|
36
|
+
(`KUI-L019`), touches its private `--_*` variables (`KUI-L020`), overrides a
|
|
37
|
+
token a typed prop sets (`KUI-L021`), or styles it through a hook class on its
|
|
38
|
+
root (`KUI-L022`). It also reports source modules that render another
|
|
39
|
+
component's public classes as their own markup (`KUI-L023`), including raw HTML
|
|
40
|
+
and DOM class writes. Raw HTML checks include complete static class tokens in
|
|
41
|
+
interpolated template literals. `KUI-L023` skips `*.test.*`, `*.spec.*`, and `__tests__/`
|
|
42
|
+
modules so output assertions are not mistaken for rendered markup. The ESLint
|
|
43
|
+
stage's `ui-component-ownership` rule reports
|
|
44
|
+
`KUI-L020` / `KUI-L021` in script. Each finding's `action` points at the
|
|
45
|
+
component's configuration, and its message routes a need with no prop to a
|
|
46
|
+
component-gap report instead of an override (see
|
|
47
|
+
[the analyzer rule](./ui-analyzer.md#component-ownership-kui-l019kui-l023)).
|
|
48
|
+
In component mode, `KUI-L019` also follows an owned wrapper to a composed
|
|
49
|
+
child's cataloged root and descendant element types. For example, a wrapper
|
|
50
|
+
around `LucideIcon` cannot size its `svg` or `path` with an element selector;
|
|
51
|
+
use the icon's props or style an app-owned element. The same check follows
|
|
52
|
+
local function components returned from the owner module and treats their
|
|
53
|
+
intrinsic markup as app-owned.
|
|
54
|
+
Unknown custom `IconNode` tags are identified when a selector explicitly
|
|
55
|
+
passes through the icon's `svg[data-lucide]` root; unrelated HTML descendants
|
|
56
|
+
without that marker remain app-owned.
|
|
57
|
+
The analyzer resolves a cataloged application component imported through an
|
|
58
|
+
exact or wildcard TypeScript `paths` alias from the nearest consumer tsconfig,
|
|
59
|
+
so the same root hook-class check applies in those files.
|
|
60
|
+
Set `"ownership": "component"` in `.kerf-ui-doctor.json` to enforce the same
|
|
61
|
+
boundary between a consumer package's own cataloged components. The default is
|
|
62
|
+
`"package"`. Selection-only entries participate too. A component owns its
|
|
63
|
+
`styleSources` (relative to its package root), or its direct relative CSS
|
|
64
|
+
imports when that list is absent, and its selection catalog `source` module.
|
|
65
|
+
Literal rendered BEM classes are inferred when those stylesheets style their
|
|
66
|
+
block. An owned root or styled literal owns its whole `block__*` and `block--*`
|
|
67
|
+
family, including dynamic names and classes placed on imported components.
|
|
68
|
+
Modules sharing one of those stylesheets co-own its classes, so their
|
|
69
|
+
own markup does not borrow from one another. A class inferred by several
|
|
70
|
+
co-owners has no arbitrary single owner for CSS subject diagnostics.
|
|
71
|
+
Set `"implicitComponentOwnership": true` to include uncataloged modules
|
|
72
|
+
that directly import CSS. Component mode reports sibling CSS and hook-class
|
|
73
|
+
restyles while allowing a component's own stylesheet and module.
|
|
74
|
+
For an entry stylesheet shared by modules without direct CSS imports, configure
|
|
75
|
+
`"ownershipGroups": [{ "styleSources": ["src/style.css"], "sources": ["src/main.tsx", "src/app/"] }]`.
|
|
76
|
+
These package-relative paths define one owner: its stylesheet and every listed
|
|
77
|
+
source file or directory prefix. Doctor reports sibling CSS restyles and markup
|
|
78
|
+
class use as `KUI-L019` and `KUI-L023`. Source paths ending in `/` match a
|
|
79
|
+
directory subtree; other paths match exactly.
|
|
80
|
+
Set `"ownershipContext": "any"` to also report a same-package sibling's class
|
|
81
|
+
used as ancestor or sibling context or within `:has()`, `:is()`, `:where()`, or
|
|
82
|
+
`:not()`. The default `"subject"` keeps subject-only context policy; classes
|
|
83
|
+
from another package may still provide context. Set
|
|
84
|
+
`"ownershipContext": "any-package"` to include cross-package component
|
|
85
|
+
classes and `[data-component]` roots in the same selector-context checks.
|
|
86
|
+
|
|
87
|
+
The browser evaluator is different: it runs the application and is disabled by default. It only runs when configuration supplies `browser.url` or the command receives `--browser-url`. Start and authorize the target application separately.
|
|
88
|
+
|
|
89
|
+
An unavailable or failed stage does not prevent independent stages from reporting. Its final exit is still a configuration failure, so a partial run cannot appear clean.
|
|
90
|
+
|
|
91
|
+
## Full and changed modes
|
|
92
|
+
|
|
93
|
+
`--full` is the default and analyzes the selected package. `--changed` reads tracked and untracked paths from Git unless one or more `--path` values are supplied. An empty changed set is a configuration error instead of a false-clean success. With `--package`, workspace-relative Git paths are converted to package-relative paths before TypeScript, ESLint, and analyzer selection; paths outside the selected package are ignored.
|
|
94
|
+
|
|
95
|
+
Full traversal treats generated/tool-owned directories (`dist`, `coverage`,
|
|
96
|
+
`node_modules`, `.git`, `.kerf-cache`, `kerf-ui-evidence`, and nested
|
|
97
|
+
`.claude/worktrees` checkouts) as outside application source. Their files are
|
|
98
|
+
excluded consistently from TypeScript, the isolated ESLint pass, analyzer
|
|
99
|
+
discovery, and cache inputs, even when one lives below another application
|
|
100
|
+
directory.
|
|
101
|
+
|
|
102
|
+
TypeScript constructs the selected package's program so compiler options retain their real meaning, while its root inputs are narrowed to changed source files. Use `--full` for release gates.
|
|
103
|
+
|
|
104
|
+
## Configuration and suppressions
|
|
105
|
+
|
|
106
|
+
Place `.kerf-ui-doctor.json` at the workspace root:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"$schema": "./node_modules/@kerfjs/ui/doctor/config.schema.json",
|
|
111
|
+
"schemaVersion": 1,
|
|
112
|
+
"mode": "full",
|
|
113
|
+
"ownership": "component",
|
|
114
|
+
"stages": { "browser": false },
|
|
115
|
+
"cache": true,
|
|
116
|
+
"failOn": "warning",
|
|
117
|
+
"suppressions": [
|
|
118
|
+
{
|
|
119
|
+
"id": "legacy-toolbar",
|
|
120
|
+
"rules": ["KUI-L006"],
|
|
121
|
+
"target": "src/legacy-toolbar.css",
|
|
122
|
+
"rationale": "Removed with the toolbar migration in the next release."
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
A suppression requires a stable id, one or more exact diagnostic ids, an exact portable source path or browser selector, and a substantive rationale. Wildcards, absolute paths, and parent traversal are rejected. Suppressed diagnostics remain in `report.suppressions` with their rationale and do not affect the exit code.
|
|
129
|
+
|
|
130
|
+
KUI suppression ids are checked against the analyzer, evaluator, doctor, installed ESLint-rule metadata, and diagnostics emitted by available tools. An unknown or stale id is a configuration error rather than a silent no-op. TypeScript `TS####` and namespaced `eslint:*` ids remain valid even when that particular run does not emit them.
|
|
131
|
+
|
|
132
|
+
The content-addressed cache lives at `.kerf-cache/ui-doctor-v1.json`. Its key covers selected source/config/catalog JSON, package manifests, root and package lockfiles, mode, paths, and configuration. Browser results are never cached. Use `--no-cache` when investigating tool installation changes not yet reflected in a lockfile.
|
|
133
|
+
|
|
134
|
+
Reports replace the workspace's absolute path with `<repo-root>` and express in-workspace files as portable relative paths. External paths are reduced to `<external>/<basename>`. URLs are excluded from the cache key. Tool messages and evidence receive the same root redaction before output or caching.
|
|
135
|
+
|
|
136
|
+
## Report and exit contract
|
|
137
|
+
|
|
138
|
+
Every diagnostic has a stable `id`, `severity`, `stage`, message, and—when applicable—an exact source location with JSON path or DOM context/selector. Analyzer evidence, catalog facts, documentation links, and safe next actions are preserved when the source tool provides them. Identical findings merge with their source stages; same identifiers with conflicting severities remain separate and add `KUI-D003`.
|
|
139
|
+
|
|
140
|
+
Exit codes are deterministic. `failOn` defaults to `error`; `--fail-on error|review|warning` overrides the config for a run. `review` fails on errors or review findings; `warning` also fails on warnings. Suppressed diagnostics never count. The effective threshold is part of the cache key.
|
|
141
|
+
|
|
142
|
+
- `0`: no active diagnostics at or above the selected threshold;
|
|
143
|
+
- `1`: repairable application findings;
|
|
144
|
+
- `2`: malformed configuration, unavailable required tooling, or a failed stage;
|
|
145
|
+
- `130`: cancellation.
|
|
146
|
+
|
|
147
|
+
Warnings and review findings remain visible at every threshold. A cached report keeps its original exit code and labels previously run stages `cached`.
|
|
148
|
+
|
|
149
|
+
## Monorepos and repair loops
|
|
150
|
+
|
|
151
|
+
`--package` accepts either a workspace package name or a workspace-relative package path. The doctor uses the selected package for compiler, lint, profile-discovery start, and analyzer scope, while workspace profile precedence and root lockfiles remain authoritative.
|
|
152
|
+
|
|
153
|
+
A repair agent should run JSON mode, apply only source-located safe changes, and rerun until exit `0`. Do not treat exit `2`, a skipped required stage, or an empty changed selection as clean. Browser evidence is objective input; the evaluator's named subjective rubric still requires human review.
|