@flood-md/agent 0.2.9 → 0.2.11
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/DESIGN.md +33 -8
- package/README.md +35 -4
- package/SKILL.md +42 -0
- package/dist/catalog.json +8716 -0
- package/dist/materials/component-accordion.md +42 -0
- package/dist/materials/component-alert-dialog.md +46 -0
- package/dist/materials/component-alert.md +41 -0
- package/dist/materials/component-app-shell.md +47 -0
- package/dist/materials/component-app-top-bar.md +36 -0
- package/dist/materials/component-aspect-ratio.md +36 -0
- package/dist/materials/component-autocomplete.md +54 -0
- package/dist/materials/component-avatar-group.md +39 -0
- package/dist/materials/component-avatar.md +40 -0
- package/dist/materials/component-badge.md +37 -0
- package/dist/materials/component-blockquote.md +35 -0
- package/dist/materials/component-breadcrumb.md +36 -0
- package/dist/materials/component-bulk-action-bar.md +52 -0
- package/dist/materials/component-button-group.md +39 -0
- package/dist/materials/component-button.md +44 -0
- package/dist/materials/component-calendar.md +59 -0
- package/dist/materials/component-card.md +46 -0
- package/dist/materials/component-center.md +39 -0
- package/dist/materials/component-character-count.md +42 -0
- package/dist/materials/component-check-card.md +39 -0
- package/dist/materials/component-checkbox-group.md +48 -0
- package/dist/materials/component-checkbox.md +37 -0
- package/dist/materials/component-code-snippet.md +37 -0
- package/dist/materials/component-collapsible.md +41 -0
- package/dist/materials/component-collection-toolbar.md +68 -0
- package/dist/materials/component-collection-view-switcher.md +49 -0
- package/dist/materials/component-color-picker.md +49 -0
- package/dist/materials/component-color-swatch.md +35 -0
- package/dist/materials/component-combobox.md +60 -0
- package/dist/materials/component-command-list.md +48 -0
- package/dist/materials/component-command-palette.md +53 -0
- package/dist/materials/component-container.md +36 -0
- package/dist/materials/component-context-menu.md +43 -0
- package/dist/materials/component-copy-button.md +43 -0
- package/dist/materials/component-data-list-item.md +40 -0
- package/dist/materials/component-data-list.md +38 -0
- package/dist/materials/component-data-table.md +70 -0
- package/dist/materials/component-date-field.md +57 -0
- package/dist/materials/component-date-picker.md +67 -0
- package/dist/materials/component-date-range-field.md +62 -0
- package/dist/materials/component-date-range-picker.md +73 -0
- package/dist/materials/component-date-time-picker.md +62 -0
- package/dist/materials/component-date-time-range-picker.md +70 -0
- package/dist/materials/component-description-list.md +42 -0
- package/dist/materials/component-dialog.md +45 -0
- package/dist/materials/component-divider.md +37 -0
- package/dist/materials/component-drawer.md +51 -0
- package/dist/materials/component-dropzone.md +45 -0
- package/dist/materials/component-editable.md +57 -0
- package/dist/materials/component-empty-state.md +41 -0
- package/dist/materials/component-error-summary.md +40 -0
- package/dist/materials/component-field.md +46 -0
- package/dist/materials/component-fieldset.md +46 -0
- package/dist/materials/component-figure.md +36 -0
- package/dist/materials/component-file-input.md +45 -0
- package/dist/materials/component-file-item.md +48 -0
- package/dist/materials/component-file-list.md +39 -0
- package/dist/materials/component-file-meta.md +36 -0
- package/dist/materials/component-filter-condition.md +53 -0
- package/dist/materials/component-filter-group.md +55 -0
- package/dist/materials/component-flood-root.md +33 -0
- package/dist/materials/component-grid.md +38 -0
- package/dist/materials/component-heading.md +38 -0
- package/dist/materials/component-hover-card.md +48 -0
- package/dist/materials/component-icon-button.md +43 -0
- package/dist/materials/component-icon-slot.md +37 -0
- package/dist/materials/component-image-preview.md +50 -0
- package/dist/materials/component-image.md +37 -0
- package/dist/materials/component-inline-code.md +36 -0
- package/dist/materials/component-inline.md +39 -0
- package/dist/materials/component-input-group.md +43 -0
- package/dist/materials/component-input.md +39 -0
- package/dist/materials/component-kbd.md +34 -0
- package/dist/materials/component-label.md +37 -0
- package/dist/materials/component-link.md +35 -0
- package/dist/materials/component-list-box.md +57 -0
- package/dist/materials/component-list.md +40 -0
- package/dist/materials/component-menu.md +54 -0
- package/dist/materials/component-menubar.md +41 -0
- package/dist/materials/component-meter.md +43 -0
- package/dist/materials/component-metric.md +39 -0
- package/dist/materials/component-multi-select.md +62 -0
- package/dist/materials/component-navigation-group.md +51 -0
- package/dist/materials/component-navigation-item.md +59 -0
- package/dist/materials/component-navigation-list.md +38 -0
- package/dist/materials/component-navigation-tree.md +51 -0
- package/dist/materials/component-number-input.md +47 -0
- package/dist/materials/component-overflow-actions.md +47 -0
- package/dist/materials/component-page-header.md +40 -0
- package/dist/materials/component-pagination.md +44 -0
- package/dist/materials/component-password-input.md +43 -0
- package/dist/materials/component-pin-input.md +45 -0
- package/dist/materials/component-popover.md +48 -0
- package/dist/materials/component-progress.md +42 -0
- package/dist/materials/component-radio-card.md +43 -0
- package/dist/materials/component-radio-group.md +49 -0
- package/dist/materials/component-radio.md +39 -0
- package/dist/materials/component-range-calendar.md +58 -0
- package/dist/materials/component-rating.md +46 -0
- package/dist/materials/component-resizable-panels.md +48 -0
- package/dist/materials/component-scroll-area.md +42 -0
- package/dist/materials/component-search-input.md +40 -0
- package/dist/materials/component-section.md +42 -0
- package/dist/materials/component-segmented-control.md +53 -0
- package/dist/materials/component-select.md +45 -0
- package/dist/materials/component-sidebar.md +64 -0
- package/dist/materials/component-skeleton.md +42 -0
- package/dist/materials/component-skip-link.md +37 -0
- package/dist/materials/component-slider.md +51 -0
- package/dist/materials/component-spinner.md +37 -0
- package/dist/materials/component-split.md +40 -0
- package/dist/materials/component-stack.md +37 -0
- package/dist/materials/component-status-indicator.md +38 -0
- package/dist/materials/component-steps.md +40 -0
- package/dist/materials/component-surface.md +37 -0
- package/dist/materials/component-switch.md +35 -0
- package/dist/materials/component-table.md +46 -0
- package/dist/materials/component-tabs.md +45 -0
- package/dist/materials/component-tag-input.md +46 -0
- package/dist/materials/component-tag.md +40 -0
- package/dist/materials/component-text.md +39 -0
- package/dist/materials/component-textarea.md +38 -0
- package/dist/materials/component-time-field.md +56 -0
- package/dist/materials/component-time-picker.md +60 -0
- package/dist/materials/component-timeline.md +38 -0
- package/dist/materials/component-toast-region.md +42 -0
- package/dist/materials/component-toggle-group.md +44 -0
- package/dist/materials/component-toggle.md +40 -0
- package/dist/materials/component-toolbar.md +46 -0
- package/dist/materials/component-tooltip.md +40 -0
- package/dist/materials/component-tree-select.md +56 -0
- package/dist/materials/component-tree.md +52 -0
- package/dist/materials/component-upload-item.md +52 -0
- package/dist/materials/component-upload-list.md +40 -0
- package/dist/materials/component-visually-hidden.md +37 -0
- package/dist/materials/guide-agent-knowledge.md +95 -0
- package/dist/materials/guide-agents.md +103 -0
- package/dist/materials/guide-faq.md +84 -0
- package/dist/materials/guide-foundations.md +275 -0
- package/dist/materials/guide-installation.md +79 -0
- package/dist/materials/guide-interface-content.md +76 -0
- package/dist/materials/guide-introduction.md +42 -0
- package/dist/materials/guide-layout.md +222 -0
- package/dist/materials/guide-motion.md +69 -0
- package/dist/materials/guide-privacy.md +23 -0
- package/dist/materials/guide-project-ui.md +127 -0
- package/dist/materials/guide-skills.md +117 -0
- package/dist/materials/guide-theming.md +73 -0
- package/dist/materials/recipe-collection-views.md +648 -0
- package/dist/materials/recipe-command-palette.md +45 -0
- package/dist/materials/recipe-filter-bar.md +68 -0
- package/dist/materials/recipe-form-submission.md +39 -0
- package/dist/materials/recipe-grouped-form.md +71 -0
- package/dist/materials/recipe-navigation-workspace.md +222 -0
- package/dist/materials/recipe-record-management.md +296 -0
- package/dist/materials/recipe-record-workspace.md +207 -0
- package/dist/materials/recipe-select-all.md +31 -0
- package/dist/materials/recipe-settings-workspace.md +232 -0
- package/dist/materials/recipe-split-button.md +26 -0
- package/dist/materials/recipe-table-bulk-actions.md +88 -0
- package/dist/materials/recipe-workspace-review.md +134 -0
- package/dist/materials/token-sys.color.accent.content.json +41 -0
- package/dist/materials/token-sys.color.action.danger.content.json +42 -0
- package/dist/materials/token-sys.color.action.ghost.content.json +41 -0
- package/dist/materials/token-sys.color.action.ghost.disabled-content.json +41 -0
- package/dist/materials/token-sys.color.action.ghost.disabled.json +41 -0
- package/dist/materials/token-sys.color.action.ghost.hover.json +41 -0
- package/dist/materials/token-sys.color.action.ghost.pressed.json +41 -0
- package/dist/materials/token-sys.color.action.ghost.rest.json +41 -0
- package/dist/materials/token-sys.color.action.on-surface.content.json +39 -0
- package/dist/materials/token-sys.color.action.on-surface.disabled-content.json +39 -0
- package/dist/materials/token-sys.color.action.on-surface.disabled.json +39 -0
- package/dist/materials/token-sys.color.action.on-surface.hover.json +39 -0
- package/dist/materials/token-sys.color.action.on-surface.pressed.json +39 -0
- package/dist/materials/token-sys.color.action.on-surface.rest.json +39 -0
- package/dist/materials/token-sys.color.action.primary.content.json +39 -0
- package/dist/materials/token-sys.color.action.primary.disabled-content.json +39 -0
- package/dist/materials/token-sys.color.action.primary.disabled.json +39 -0
- package/dist/materials/token-sys.color.action.primary.hover.json +39 -0
- package/dist/materials/token-sys.color.action.primary.pressed.json +39 -0
- package/dist/materials/token-sys.color.action.primary.rest.json +39 -0
- package/dist/materials/token-sys.color.action.subtle.content.json +39 -0
- package/dist/materials/token-sys.color.action.subtle.disabled-content.json +39 -0
- package/dist/materials/token-sys.color.action.subtle.disabled.json +39 -0
- package/dist/materials/token-sys.color.action.subtle.hover.json +39 -0
- package/dist/materials/token-sys.color.action.subtle.pressed.json +39 -0
- package/dist/materials/token-sys.color.action.subtle.rest.json +39 -0
- package/dist/materials/token-sys.color.border.decorative.json +42 -0
- package/dist/materials/token-sys.color.border.default.json +39 -0
- package/dist/materials/token-sys.color.border.floating.json +39 -0
- package/dist/materials/token-sys.color.border.strong.json +39 -0
- package/dist/materials/token-sys.color.content.default.json +41 -0
- package/dist/materials/token-sys.color.content.helper.json +39 -0
- package/dist/materials/token-sys.color.content.subtle.json +43 -0
- package/dist/materials/token-sys.color.context.base.action-disabled.json +41 -0
- package/dist/materials/token-sys.color.context.base.action-hover.json +41 -0
- package/dist/materials/token-sys.color.context.base.action-pressed.json +41 -0
- package/dist/materials/token-sys.color.context.base.action-rest.json +41 -0
- package/dist/materials/token-sys.color.context.base.avatar-background.json +41 -0
- package/dist/materials/token-sys.color.context.base.border-decorative.json +41 -0
- package/dist/materials/token-sys.color.context.base.divider-decorative.json +41 -0
- package/dist/materials/token-sys.color.context.base.field-background.json +41 -0
- package/dist/materials/token-sys.color.context.base.field-border.json +41 -0
- package/dist/materials/token-sys.color.context.base.field-disabled.json +41 -0
- package/dist/materials/token-sys.color.context.base.field-readonly.json +41 -0
- package/dist/materials/token-sys.color.context.base.indicator-track.json +41 -0
- package/dist/materials/token-sys.color.context.base.loading-placeholder.json +41 -0
- package/dist/materials/token-sys.color.context.base.muted-background.json +41 -0
- package/dist/materials/token-sys.color.context.base.on-surface-action-disabled.json +41 -0
- package/dist/materials/token-sys.color.context.base.on-surface-action-hover.json +41 -0
- package/dist/materials/token-sys.color.context.base.on-surface-action-pressed.json +41 -0
- package/dist/materials/token-sys.color.context.base.on-surface-action-rest.json +41 -0
- package/dist/materials/token-sys.color.context.base.selection-background.json +41 -0
- package/dist/materials/token-sys.color.context.base.supporting-background.json +41 -0
- package/dist/materials/token-sys.color.context.filled.action-disabled.json +41 -0
- package/dist/materials/token-sys.color.context.filled.action-hover.json +41 -0
- package/dist/materials/token-sys.color.context.filled.action-pressed.json +41 -0
- package/dist/materials/token-sys.color.context.filled.action-rest.json +41 -0
- package/dist/materials/token-sys.color.context.filled.avatar-background.json +41 -0
- package/dist/materials/token-sys.color.context.filled.border-decorative.json +41 -0
- package/dist/materials/token-sys.color.context.filled.divider-decorative.json +41 -0
- package/dist/materials/token-sys.color.context.filled.field-background.json +41 -0
- package/dist/materials/token-sys.color.context.filled.field-border.json +41 -0
- package/dist/materials/token-sys.color.context.filled.field-disabled.json +41 -0
- package/dist/materials/token-sys.color.context.filled.field-readonly.json +41 -0
- package/dist/materials/token-sys.color.context.filled.indicator-track.json +41 -0
- package/dist/materials/token-sys.color.context.filled.loading-placeholder.json +41 -0
- package/dist/materials/token-sys.color.context.filled.muted-background.json +41 -0
- package/dist/materials/token-sys.color.context.filled.selection-background.json +41 -0
- package/dist/materials/token-sys.color.context.filled.supporting-background.json +41 -0
- package/dist/materials/token-sys.color.divider.decorative.json +41 -0
- package/dist/materials/token-sys.color.feedback.error.json +39 -0
- package/dist/materials/token-sys.color.feedback.info.json +39 -0
- package/dist/materials/token-sys.color.feedback.success.json +39 -0
- package/dist/materials/token-sys.color.feedback.warning.json +39 -0
- package/dist/materials/token-sys.color.field.background.json +39 -0
- package/dist/materials/token-sys.color.field.border.json +39 -0
- package/dist/materials/token-sys.color.field.boundary.json +39 -0
- package/dist/materials/token-sys.color.field.content.json +39 -0
- package/dist/materials/token-sys.color.field.disabled-content.json +39 -0
- package/dist/materials/token-sys.color.field.disabled.json +39 -0
- package/dist/materials/token-sys.color.field.placeholder.json +39 -0
- package/dist/materials/token-sys.color.field.readonly.json +41 -0
- package/dist/materials/token-sys.color.focus.ring.json +39 -0
- package/dist/materials/token-sys.color.indicator.accent.json +39 -0
- package/dist/materials/token-sys.color.indicator.fill.json +39 -0
- package/dist/materials/token-sys.color.indicator.track.json +39 -0
- package/dist/materials/token-sys.color.loading.placeholder.json +42 -0
- package/dist/materials/token-sys.color.scrollbar.thumb-hover.json +39 -0
- package/dist/materials/token-sys.color.scrollbar.thumb.json +39 -0
- package/dist/materials/token-sys.color.selection.background.json +39 -0
- package/dist/materials/token-sys.color.selection.content.json +39 -0
- package/dist/materials/token-sys.color.selection.floating.json +39 -0
- package/dist/materials/token-sys.color.shadow.floating.json +39 -0
- package/dist/materials/token-sys.color.surface.avatar.json +41 -0
- package/dist/materials/token-sys.color.surface.canvas.json +39 -0
- package/dist/materials/token-sys.color.surface.current.json +41 -0
- package/dist/materials/token-sys.color.surface.default.json +39 -0
- package/dist/materials/token-sys.color.surface.floating-translucent.json +39 -0
- package/dist/materials/token-sys.color.surface.floating.json +39 -0
- package/dist/materials/token-sys.color.surface.muted.json +41 -0
- package/dist/materials/token-sys.color.surface.overlay.json +39 -0
- package/dist/materials/token-sys.color.surface.raised.json +39 -0
- package/dist/materials/token-sys.color.surface.supporting.json +41 -0
- package/dist/materials/token-sys.control.2xl.gap.json +34 -0
- package/dist/materials/token-sys.control.2xl.height.json +34 -0
- package/dist/materials/token-sys.control.2xl.icon-size.json +34 -0
- package/dist/materials/token-sys.control.2xl.line-height.json +31 -0
- package/dist/materials/token-sys.control.2xl.padding-inline.json +34 -0
- package/dist/materials/token-sys.control.2xl.text-size.json +34 -0
- package/dist/materials/token-sys.control.2xs.gap.json +34 -0
- package/dist/materials/token-sys.control.2xs.height.json +34 -0
- package/dist/materials/token-sys.control.2xs.icon-size.json +34 -0
- package/dist/materials/token-sys.control.2xs.line-height.json +31 -0
- package/dist/materials/token-sys.control.2xs.padding-inline.json +34 -0
- package/dist/materials/token-sys.control.2xs.text-size.json +34 -0
- package/dist/materials/token-sys.control.lg.gap.json +34 -0
- package/dist/materials/token-sys.control.lg.height.json +34 -0
- package/dist/materials/token-sys.control.lg.icon-size.json +34 -0
- package/dist/materials/token-sys.control.lg.line-height.json +31 -0
- package/dist/materials/token-sys.control.lg.padding-inline.json +34 -0
- package/dist/materials/token-sys.control.lg.text-size.json +34 -0
- package/dist/materials/token-sys.control.md.gap.json +34 -0
- package/dist/materials/token-sys.control.md.height.json +34 -0
- package/dist/materials/token-sys.control.md.icon-size.json +34 -0
- package/dist/materials/token-sys.control.md.line-height.json +31 -0
- package/dist/materials/token-sys.control.md.padding-inline.json +34 -0
- package/dist/materials/token-sys.control.md.text-size.json +34 -0
- package/dist/materials/token-sys.control.sm.gap.json +34 -0
- package/dist/materials/token-sys.control.sm.height.json +34 -0
- package/dist/materials/token-sys.control.sm.icon-size.json +34 -0
- package/dist/materials/token-sys.control.sm.line-height.json +31 -0
- package/dist/materials/token-sys.control.sm.padding-inline.json +34 -0
- package/dist/materials/token-sys.control.sm.text-size.json +34 -0
- package/dist/materials/token-sys.control.xl.gap.json +34 -0
- package/dist/materials/token-sys.control.xl.height.json +34 -0
- package/dist/materials/token-sys.control.xl.icon-size.json +34 -0
- package/dist/materials/token-sys.control.xl.line-height.json +31 -0
- package/dist/materials/token-sys.control.xl.padding-inline.json +34 -0
- package/dist/materials/token-sys.control.xl.text-size.json +34 -0
- package/dist/materials/token-sys.control.xs.gap.json +34 -0
- package/dist/materials/token-sys.control.xs.height.json +34 -0
- package/dist/materials/token-sys.control.xs.icon-size.json +34 -0
- package/dist/materials/token-sys.control.xs.line-height.json +31 -0
- package/dist/materials/token-sys.control.xs.padding-inline.json +34 -0
- package/dist/materials/token-sys.control.xs.text-size.json +34 -0
- package/dist/materials/token-sys.effect.floating.blur.json +34 -0
- package/dist/materials/token-sys.effect.floating.saturation.json +31 -0
- package/dist/materials/token-sys.effect.floating.shadow-blur.json +34 -0
- package/dist/materials/token-sys.effect.floating.shadow-offset.json +34 -0
- package/dist/materials/token-sys.focus.offset.json +34 -0
- package/dist/materials/token-sys.focus.width.json +34 -0
- package/dist/materials/token-sys.font.mono.json +36 -0
- package/dist/materials/token-sys.font.sans.json +35 -0
- package/dist/materials/token-sys.layer.base.json +31 -0
- package/dist/materials/token-sys.layer.modal.json +31 -0
- package/dist/materials/token-sys.layer.popover.json +31 -0
- package/dist/materials/token-sys.layer.sticky.json +31 -0
- package/dist/materials/token-sys.layer.toast.json +31 -0
- package/dist/materials/token-sys.motion.distance-panel.json +36 -0
- package/dist/materials/token-sys.motion.distance-small.json +36 -0
- package/dist/materials/token-sys.motion.duration-enter.json +36 -0
- package/dist/materials/token-sys.motion.duration-exit.json +36 -0
- package/dist/materials/token-sys.motion.duration-fast.json +34 -0
- package/dist/materials/token-sys.motion.duration-layer.json +36 -0
- package/dist/materials/token-sys.motion.duration-loop.json +36 -0
- package/dist/materials/token-sys.motion.duration-normal.json +34 -0
- package/dist/materials/token-sys.motion.duration-press.json +36 -0
- package/dist/materials/token-sys.motion.duration-reduced.json +34 -0
- package/dist/materials/token-sys.motion.easing-enter.json +38 -0
- package/dist/materials/token-sys.motion.easing-exit.json +38 -0
- package/dist/materials/token-sys.motion.easing.json +36 -0
- package/dist/materials/token-sys.radius.action.json +34 -0
- package/dist/materials/token-sys.radius.block.json +34 -0
- package/dist/materials/token-sys.radius.field.json +34 -0
- package/dist/materials/token-sys.radius.indicator.json +34 -0
- package/dist/materials/token-sys.radius.surface.json +34 -0
- package/dist/materials/token-sys.scrollbar.inset.json +34 -0
- package/dist/materials/token-sys.scrollbar.min-thumb.json +34 -0
- package/dist/materials/token-sys.scrollbar.size.json +34 -0
- package/dist/materials/token-sys.size.choice.json +34 -0
- package/dist/materials/token-sys.size.container.lg.json +37 -0
- package/dist/materials/token-sys.size.container.md.json +37 -0
- package/dist/materials/token-sys.size.container.sm.json +37 -0
- package/dist/materials/token-sys.size.grid.min-item.json +37 -0
- package/dist/materials/token-sys.size.indicator.lg.json +34 -0
- package/dist/materials/token-sys.size.indicator.md.json +34 -0
- package/dist/materials/token-sys.size.indicator.sm.json +34 -0
- package/dist/materials/token-sys.size.keycap.lg.json +34 -0
- package/dist/materials/token-sys.size.keycap.md.json +34 -0
- package/dist/materials/token-sys.size.keycap.sm.json +34 -0
- package/dist/materials/token-sys.size.switch-height.json +34 -0
- package/dist/materials/token-sys.size.switch-thumb.json +34 -0
- package/dist/materials/token-sys.size.switch-width.json +34 -0
- package/dist/materials/token-sys.size.touch.json +34 -0
- package/dist/materials/token-sys.space.0.json +34 -0
- package/dist/materials/token-sys.space.1.json +34 -0
- package/dist/materials/token-sys.space.12.json +34 -0
- package/dist/materials/token-sys.space.2.json +34 -0
- package/dist/materials/token-sys.space.3.json +34 -0
- package/dist/materials/token-sys.space.4.json +34 -0
- package/dist/materials/token-sys.space.6.json +34 -0
- package/dist/materials/token-sys.space.8.json +34 -0
- package/dist/materials/token-sys.stroke.width.json +34 -0
- package/dist/materials/token-sys.type.body.line-height.json +31 -0
- package/dist/materials/token-sys.type.body.size.json +34 -0
- package/dist/materials/token-sys.type.body.tracking.json +34 -0
- package/dist/materials/token-sys.type.body.weight.json +31 -0
- package/dist/materials/token-sys.type.compact.line-height.json +31 -0
- package/dist/materials/token-sys.type.compact.size.json +34 -0
- package/dist/materials/token-sys.type.compact.tracking.json +34 -0
- package/dist/materials/token-sys.type.compact.weight.json +31 -0
- package/dist/materials/token-sys.type.control.line-height.json +31 -0
- package/dist/materials/token-sys.type.control.size.json +34 -0
- package/dist/materials/token-sys.type.control.tracking.json +34 -0
- package/dist/materials/token-sys.type.control.weight.json +31 -0
- package/dist/materials/token-sys.type.dialog-title.line-height.json +33 -0
- package/dist/materials/token-sys.type.dialog-title.size.json +36 -0
- package/dist/materials/token-sys.type.dialog-title.tracking.json +36 -0
- package/dist/materials/token-sys.type.dialog-title.weight.json +33 -0
- package/dist/materials/token-sys.type.display.line-height.json +31 -0
- package/dist/materials/token-sys.type.display.size.json +34 -0
- package/dist/materials/token-sys.type.display.tracking.json +34 -0
- package/dist/materials/token-sys.type.display.weight.json +31 -0
- package/dist/materials/token-sys.type.field-label.line-height.json +33 -0
- package/dist/materials/token-sys.type.field-label.size.json +36 -0
- package/dist/materials/token-sys.type.field-label.tracking.json +36 -0
- package/dist/materials/token-sys.type.field-label.weight.json +33 -0
- package/dist/materials/token-sys.type.group-title.line-height.json +33 -0
- package/dist/materials/token-sys.type.group-title.size.json +36 -0
- package/dist/materials/token-sys.type.group-title.tracking.json +36 -0
- package/dist/materials/token-sys.type.group-title.weight.json +33 -0
- package/dist/materials/token-sys.type.helper.line-height.json +31 -0
- package/dist/materials/token-sys.type.helper.size.json +34 -0
- package/dist/materials/token-sys.type.helper.tracking.json +34 -0
- package/dist/materials/token-sys.type.helper.weight.json +31 -0
- package/dist/materials/token-sys.type.label.line-height.json +31 -0
- package/dist/materials/token-sys.type.label.size.json +34 -0
- package/dist/materials/token-sys.type.label.tracking.json +34 -0
- package/dist/materials/token-sys.type.label.weight.json +31 -0
- package/dist/materials/token-sys.type.large.line-height.json +31 -0
- package/dist/materials/token-sys.type.large.size.json +34 -0
- package/dist/materials/token-sys.type.large.tracking.json +34 -0
- package/dist/materials/token-sys.type.large.weight.json +31 -0
- package/dist/materials/token-sys.type.lead.line-height.json +31 -0
- package/dist/materials/token-sys.type.lead.size.json +34 -0
- package/dist/materials/token-sys.type.lead.tracking.json +34 -0
- package/dist/materials/token-sys.type.lead.weight.json +31 -0
- package/dist/materials/token-sys.type.page.line-height.json +31 -0
- package/dist/materials/token-sys.type.page.size.json +34 -0
- package/dist/materials/token-sys.type.page.tracking.json +34 -0
- package/dist/materials/token-sys.type.page.weight.json +31 -0
- package/dist/materials/token-sys.type.section.line-height.json +31 -0
- package/dist/materials/token-sys.type.section.size.json +34 -0
- package/dist/materials/token-sys.type.section.tracking.json +34 -0
- package/dist/materials/token-sys.type.section.weight.json +31 -0
- package/knowledge.json +522 -0
- package/lib/index.d.ts +33 -0
- package/lib/index.mjs +144 -0
- package/lib/inspect.d.ts +12 -0
- package/lib/inspect.mjs +187 -0
- package/manifest.json +51 -1
- package/package.json +13 -1
- package/references/component-families.md +146 -0
- package/references/composition.md +4 -4
- package/references/customization.md +14 -0
- package/references/flood-composition/SKILL.md +34 -0
- package/references/flood-data-display/SKILL.md +149 -0
- package/references/flood-feedback/SKILL.md +49 -3
- package/references/flood-forms/SKILL.md +82 -2
- package/references/flood-foundations/SKILL.md +43 -0
- package/references/flood-hierarchy/SKILL.md +103 -0
- package/references/flood-interface-content/SKILL.md +163 -0
- package/references/flood-motion/SKILL.md +44 -0
- package/references/flood-navigation/SKILL.md +195 -0
- package/references/flood-overlays/SKILL.md +42 -0
- package/references/flood-records/SKILL.md +148 -0
- package/references/flood-spacing/SKILL.md +54 -0
- package/references/flood-verification/SKILL.md +72 -0
- package/references/interface-content-examples.md +51 -0
- package/references/knowledge.md +49 -0
- package/references/record-decisions.md +25 -0
- package/references/rendered-review.md +102 -0
- package/references/routing.md +1 -0
- package/references/spacing.md +30 -0
- package/rules.json +242 -6
- package/workflows.json +171 -10
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { readFile, lstat, realpath, open } from 'node:fs/promises';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { dirname, resolve, relative, isAbsolute, sep } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
7
|
+
const KINDS = new Set(['guide', 'workflow', 'reference', 'component', 'recipe', 'token']);
|
|
8
|
+
const ID = /^[a-z][a-z0-9-]*:[a-z0-9][a-z0-9.-]*$/;
|
|
9
|
+
const MAX_CATALOG_BYTES = 8 * 1024 * 1024;
|
|
10
|
+
const MAX_MATERIAL_BYTES = 1024 * 1024;
|
|
11
|
+
const PAGE_BYTES = 16 * 1024;
|
|
12
|
+
const digest = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
13
|
+
const fail = (code, message) => { const error = new Error(message); error.code = code; throw error; };
|
|
14
|
+
const inside = (root, path) => { const r = relative(root, path); return r === '' || (r !== '..' && !r.startsWith('..' + sep) && !isAbsolute(r)); };
|
|
15
|
+
const safePath = path => typeof path === 'string' && /^[a-zA-Z0-9._/-]+$/.test(path) && !path.startsWith('/') && !path.split('/').some(part => !part || part === '.' || part === '..');
|
|
16
|
+
const text = (value, limit) => typeof value === 'string' && value.trim().length > 0 && value.length <= limit;
|
|
17
|
+
|
|
18
|
+
/** Validate the data contract without loading material bodies or executing instructions. */
|
|
19
|
+
export function validateKnowledgeCatalog(catalog) {
|
|
20
|
+
if (catalog?.schemaVersion !== 1 || !/^\d+\.\d+\.\d+$/.test(catalog.version ?? '') || !Array.isArray(catalog.groups) || !catalog.groups.length || catalog.groups.length > 16 || !Array.isArray(catalog.materials) || !catalog.materials.length || !Array.isArray(catalog.capabilities) || catalog.capabilities.length > 32) fail('INVALID_CATALOG', 'Unsupported or incomplete Flood knowledge catalog.');
|
|
21
|
+
const groups = new Set(), ids = new Set(), files = new Set();
|
|
22
|
+
for (const group of catalog.groups) {
|
|
23
|
+
if (!/^[a-z][a-z0-9-]*$/.test(group.id ?? '') || groups.has(group.id) || !text(group.name, 128) || !text(group.description, 256)) fail('INVALID_CATALOG', 'Invalid or duplicate knowledge group.');
|
|
24
|
+
groups.add(group.id);
|
|
25
|
+
}
|
|
26
|
+
let previous = '';
|
|
27
|
+
for (const item of catalog.materials) {
|
|
28
|
+
if (!ID.test(item.id ?? '') || ids.has(item.id) || item.id <= previous || !KINDS.has(item.kind) || !item.id.startsWith(item.kind + ':') || !text(item.name, 128) || !text(item.description, 1024) || !safePath(item.file) || files.has(item.file) || !/^[a-f0-9]{64}$/.test(item.sha256 ?? '') || !Number.isSafeInteger(item.bytes) || item.bytes < 1 || item.bytes > MAX_MATERIAL_BYTES || !Array.isArray(item.groups) || !item.groups.length || item.groups.length > 8 || new Set(item.groups).size !== item.groups.length || item.groups.some(group => !groups.has(group)) || !Array.isArray(item.links) || !Array.isArray(item.requires) || item.status !== 'available') fail('INVALID_CATALOG', 'Invalid, unsorted or duplicate knowledge material.');
|
|
29
|
+
if (item.installedTarget !== undefined && (!safePath(item.installedTarget) || !(item.installedTarget.startsWith('flood/') || item.installedTarget.startsWith('.agents/skills/flood-ui/')))) fail('INVALID_CATALOG', 'Invalid owned guide target.');
|
|
30
|
+
if (item.requires.length > 16 || item.links.length > 256 || new Set(item.requires).size !== item.requires.length || new Set(item.links).size !== item.links.length) fail('INVALID_CATALOG', 'Knowledge relationships exceed their per-material budget or contain duplicates.');
|
|
31
|
+
ids.add(item.id); files.add(item.file); previous = item.id;
|
|
32
|
+
}
|
|
33
|
+
const byId = new Map(catalog.materials.map(item => [item.id, item]));
|
|
34
|
+
for (const item of catalog.materials) for (const id of [...item.links, ...item.requires]) if (!ids.has(id)) fail('INVALID_CATALOG', `Unresolved knowledge relationship: ${id}`);
|
|
35
|
+
const visiting = new Set(), depths = new Map();
|
|
36
|
+
const visit = id => {
|
|
37
|
+
if (visiting.has(id)) fail('INVALID_CATALOG', 'Knowledge prerequisite cycle.');
|
|
38
|
+
if (depths.has(id)) return depths.get(id);
|
|
39
|
+
if (visiting.size >= 32) fail('INVALID_CATALOG', 'Knowledge prerequisites exceed the supported depth.');
|
|
40
|
+
visiting.add(id);
|
|
41
|
+
let depth = 1;
|
|
42
|
+
for (const dependency of byId.get(id).requires) depth = Math.max(depth, 1 + visit(dependency));
|
|
43
|
+
visiting.delete(id);
|
|
44
|
+
if (depth > 32) fail('INVALID_CATALOG', 'Knowledge prerequisites exceed the supported depth.');
|
|
45
|
+
depths.set(id, depth); return depth;
|
|
46
|
+
};
|
|
47
|
+
for (const id of ids) visit(id);
|
|
48
|
+
const capabilities = new Set();
|
|
49
|
+
for (const item of catalog.capabilities) {
|
|
50
|
+
if (!/^[a-z][a-z0-9-]*$/.test(item.id ?? '') || capabilities.has(item.id) || !['available', 'not-provided'].includes(item.status) || !text(item.description, 256) || !Array.isArray(item.materials) || item.materials.length > 16 || item.materials.some(id => !ids.has(id)) || (item.status === 'available' && !item.materials.length) || (item.status === 'not-provided' && item.materials.length)) fail('INVALID_CATALOG', 'Invalid knowledge capability declaration.');
|
|
51
|
+
capabilities.add(item.id);
|
|
52
|
+
}
|
|
53
|
+
return catalog;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function readRegular(root, path, maxBytes) {
|
|
57
|
+
if (!safePath(path)) fail('INVALID_PATH', 'Knowledge paths must come from the package catalog.');
|
|
58
|
+
const base = await realpath(root);
|
|
59
|
+
let current = base;
|
|
60
|
+
for (const part of path.split('/')) {
|
|
61
|
+
current = resolve(current, part);
|
|
62
|
+
const stat = await lstat(current).catch(() => fail('MISSING_MATERIAL', `Missing knowledge file: ${path}`));
|
|
63
|
+
if (stat.isSymbolicLink()) fail('INVALID_PATH', 'Refusing a linked knowledge file or directory.');
|
|
64
|
+
}
|
|
65
|
+
if (!inside(base, await realpath(current))) fail('INVALID_PATH', 'Knowledge file left its package root.');
|
|
66
|
+
const handle = await open(current, 'r');
|
|
67
|
+
try {
|
|
68
|
+
const stat = await handle.stat();
|
|
69
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.size > maxBytes) fail('INVALID_MATERIAL', 'Knowledge file is not a bounded regular file.');
|
|
70
|
+
const bytes = await handle.readFile();
|
|
71
|
+
if (bytes.length > maxBytes) fail('INVALID_MATERIAL', 'Knowledge file exceeds its read budget.');
|
|
72
|
+
return bytes;
|
|
73
|
+
} finally { await handle.close(); }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const decode = bytes => { try { return new TextDecoder('utf-8', { fatal: true }).decode(bytes); } catch { fail('INVALID_MATERIAL', 'Knowledge must be valid UTF-8.'); } };
|
|
77
|
+
const summary = ({id, kind, name, description, groups, status, links, requires}) => ({id, kind, name, description, groups: [...groups], status, relatedCount: links.length, requires: [...requires]});
|
|
78
|
+
|
|
79
|
+
/** A provider-neutral, read-only reader. A fresh catalog is checked on every operation. */
|
|
80
|
+
export function createKnowledgeReader({root = DEFAULT_ROOT} = {}) {
|
|
81
|
+
async function load() {
|
|
82
|
+
const bytes = await readRegular(root, 'dist/catalog.json', MAX_CATALOG_BYTES);
|
|
83
|
+
let catalog;
|
|
84
|
+
try { catalog = JSON.parse(decode(bytes)); } catch (error) { if (error.code) throw error; fail('INVALID_CATALOG', 'Malformed Flood knowledge catalog.'); }
|
|
85
|
+
validateKnowledgeCatalog(catalog);
|
|
86
|
+
const pkg = JSON.parse(decode(await readRegular(root, 'package.json', 64 * 1024)));
|
|
87
|
+
if (pkg.name !== '@flood-md/agent' || pkg.version !== catalog.version) fail('VERSION_MISMATCH', 'Agent package and knowledge catalog versions differ.');
|
|
88
|
+
return {catalog, fingerprint: digest(bytes)};
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
async info() {
|
|
92
|
+
const {catalog, fingerprint} = await load();
|
|
93
|
+
return {schemaVersion: 1, version: catalog.version, fingerprint, materialCount: catalog.materials.length,
|
|
94
|
+
groups: catalog.groups.map(group => ({...group, count: catalog.materials.filter(item => item.groups.includes(group.id)).length})),
|
|
95
|
+
kinds: [...KINDS].map(kind => ({kind, count: catalog.materials.filter(item => item.kind === kind).length})),
|
|
96
|
+
capabilities: structuredClone(catalog.capabilities)};
|
|
97
|
+
},
|
|
98
|
+
async list({kind, group, search = '', limit = 12, cursor} = {}) {
|
|
99
|
+
if ((kind !== undefined && !KINDS.has(kind)) || !Number.isSafeInteger(limit) || limit < 1 || limit > 50 || typeof search !== 'string' || search.length > 160) fail('INVALID_QUERY', 'Invalid catalog kind, limit or search.');
|
|
100
|
+
const {catalog, fingerprint} = await load();
|
|
101
|
+
if (group !== undefined && !catalog.groups.some(item => item.id === group)) fail('INVALID_QUERY', 'Unknown knowledge group.');
|
|
102
|
+
const query = {kind: kind ?? null, group: group ?? null, search: search.trim().toLowerCase()};
|
|
103
|
+
const queryHash = digest(JSON.stringify(query));
|
|
104
|
+
let offset = 0;
|
|
105
|
+
if (cursor !== undefined) {
|
|
106
|
+
let state;
|
|
107
|
+
try { if (typeof cursor !== 'string' || cursor.length > 1024 || !/^[A-Za-z0-9_-]+$/.test(cursor)) throw Error(); state = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')); } catch { fail('INVALID_CURSOR', 'Malformed knowledge cursor.'); }
|
|
108
|
+
if (state.fingerprint !== fingerprint || state.query !== queryHash || !Number.isSafeInteger(state.offset) || state.offset < 1) fail('STALE_CURSOR', 'Catalog or query changed; start a fresh catalog page.');
|
|
109
|
+
offset = state.offset;
|
|
110
|
+
}
|
|
111
|
+
const matches = catalog.materials.filter(item => (!kind || item.kind === kind) && (!group || item.groups.includes(group)) && (!query.search || `${item.id} ${item.name} ${item.description}`.toLowerCase().includes(query.search)));
|
|
112
|
+
if (offset > matches.length) fail('INVALID_CURSOR', 'Knowledge cursor is outside the result set.');
|
|
113
|
+
const page = [];
|
|
114
|
+
const response = () => {
|
|
115
|
+
const next = offset + page.length;
|
|
116
|
+
return {schemaVersion: 1, version: catalog.version, fingerprint, query, total: matches.length, items: page,
|
|
117
|
+
truncated: next < matches.length, nextCursor: next < matches.length ? Buffer.from(JSON.stringify({fingerprint, query: queryHash, offset: next})).toString('base64url') : null};
|
|
118
|
+
};
|
|
119
|
+
for (const item of matches.slice(offset, offset + limit)) {
|
|
120
|
+
page.push(summary(item));
|
|
121
|
+
if (Buffer.byteLength(JSON.stringify(response())) > PAGE_BYTES) { page.pop(); break; }
|
|
122
|
+
}
|
|
123
|
+
if (offset < matches.length && !page.length) fail('INVALID_CATALOG', 'A catalog entry exceeds the discovery budget.');
|
|
124
|
+
return response();
|
|
125
|
+
},
|
|
126
|
+
async read(id, {offset = 0, length = 10000, linksOffset = 0} = {}) {
|
|
127
|
+
if (!ID.test(id ?? '') || !Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(length) || length < 1 || length > 40000 || !Number.isSafeInteger(linksOffset) || linksOffset < 0) fail('INVALID_QUERY', 'Use a known material ID and a bounded character range.');
|
|
128
|
+
const {catalog, fingerprint} = await load();
|
|
129
|
+
const item = catalog.materials.find(item => item.id === id);
|
|
130
|
+
if (!item) fail('UNKNOWN_MATERIAL', `Unknown installed knowledge material: ${id}`);
|
|
131
|
+
if (linksOffset > item.links.length) fail('INVALID_QUERY', 'Link offset is outside the material.');
|
|
132
|
+
const bytes = await readRegular(root, item.file, MAX_MATERIAL_BYTES);
|
|
133
|
+
if (bytes.length !== item.bytes || digest(bytes) !== item.sha256) fail('INTEGRITY_MISMATCH', 'Knowledge content differs from the versioned catalog.');
|
|
134
|
+
const chars = Array.from(decode(bytes));
|
|
135
|
+
if (offset > chars.length) fail('INVALID_QUERY', 'Read offset is outside the material.');
|
|
136
|
+
const end = Math.min(chars.length, offset + length);
|
|
137
|
+
return {schemaVersion: 1, version: catalog.version, fingerprint, ...summary(item), source: 'package',
|
|
138
|
+
sha256: item.sha256, installedTarget: item.installedTarget ?? null, links: item.links.slice(linksOffset, linksOffset + 32),
|
|
139
|
+
linksOffset, nextLinksOffset: linksOffset + 32 < item.links.length ? linksOffset + 32 : null,
|
|
140
|
+
text: chars.slice(offset, end).join(''), offset, totalCharacters: chars.length,
|
|
141
|
+
truncated: end < chars.length, nextOffset: end < chars.length ? end : null};
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
package/lib/inspect.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface InspectionTarget {tag:string;id:string|null;className:string;label:string;}
|
|
2
|
+
export interface InspectionIssue {id:'icons.text-glyph'|'icons.unslotted-svg'|'table.mixed-row-centre'|'table.word-fragment'|'copy.action-echo'|'copy.unowned-support'|'forms.track-drift'|'forms.content-track-drift'|'layout.page-overflow';target:InspectionTarget;related?:InspectionTarget;message:string;offset?:number;word?:string;}
|
|
3
|
+
export interface RenderedUIInspection {
|
|
4
|
+
schemaVersion:1;issues:InspectionIssue[];copy:InspectionTarget[];
|
|
5
|
+
fields:(InspectionTarget&{x:number;y:number;width:number;height:number})[];
|
|
6
|
+
truncated:boolean;notChecked:string[];
|
|
7
|
+
}
|
|
8
|
+
/** Invoke inside a browser document, e.g. page.evaluate(inspectRenderedUI). No mutation or network calls. */
|
|
9
|
+
export declare function inspectRenderedUI(options?:{rootSelector?:string;maxElements?:number}):RenderedUIInspection;
|
|
10
|
+
export interface SourceUIIssue {id:'composition.native-dialog'|'composition.native-control'|'composition.layout-owner'|'composition.scaled-minimum';line:number;column:number;excerpt:string;owner:string;message:string;}
|
|
11
|
+
/** Text observations, not an AST or conformance check. No file access, execution or network. */
|
|
12
|
+
export declare function inspectSourceUI(source:string,options?:{format?:'jsx'|'css';maxFindings?:number}):{schemaVersion:1;issues:SourceUIIssue[];truncated:boolean;notChecked:string[]};
|
package/lib/inspect.mjs
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/** Optional text observations for host-owned JSX/CSS; no parsing or execution. */
|
|
2
|
+
export function inspectSourceUI(source,options={}) {
|
|
3
|
+
if(typeof source!=='string'||source.length>500000)throw new Error('Source must be a string of at most 500000 UTF-16 units');
|
|
4
|
+
const limit=options.maxFindings??50,format=options.format??'jsx';
|
|
5
|
+
if(!Number.isSafeInteger(limit)||limit<1||limit>500)throw new Error('maxFindings must be an integer from 1 to 500');
|
|
6
|
+
if(!['jsx','css'].includes(format))throw new Error('format must be jsx or css');
|
|
7
|
+
const issues=[];let truncated=false;
|
|
8
|
+
const record=(match,id,owner,message)=>{
|
|
9
|
+
if(issues.length>=limit){truncated=true;return;}
|
|
10
|
+
const offset=match.index+match[0].length-match[0].trimStart().length;
|
|
11
|
+
const prefix=source.slice(0,offset),line=prefix.split('\n').length,column=offset-prefix.lastIndexOf('\n');
|
|
12
|
+
issues.push({id,line,column,excerpt:match[0].trimStart().slice(0,180),owner,message});
|
|
13
|
+
};
|
|
14
|
+
if(format==='jsx'){
|
|
15
|
+
for(const match of source.matchAll(/\b(?:window|globalThis)\.(confirm|alert|prompt)\s*\(/g))record(match,'composition.native-dialog',match[1]==='confirm'?'AlertDialog':match[1]==='prompt'?'Dialog':'Alert','Native browser dialog candidate. Review the actual call and use the installed Flood owner unless native UI is explicitly required.');
|
|
16
|
+
for(const match of source.matchAll(/<(select|button)\b|<input\b[^>]*\btype\s*=\s*["'](date|time|checkbox|radio|range)["']/g)){
|
|
17
|
+
const role=match[1]||match[2],owner={select:'Select',button:'Button',date:'DatePicker',time:'TimeField',checkbox:'Checkbox',radio:'Radio',range:'Slider'}[role];
|
|
18
|
+
record(match,'composition.native-control',owner,'Raw control candidate. Review whether an existing Flood component owns this role; native form/table structure and deliberate documented adapters are separate cases.');
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
for(const match of source.matchAll(/([^{}]+)\{[^{}]*\bdisplay\s*:\s*(grid|flex)\b/g)){
|
|
22
|
+
if(match[1].trim().startsWith('.flood-'))continue;
|
|
23
|
+
record(match,'composition.layout-owner',match[2]==='grid'?'Grid':'Stack / Inline','Custom layout candidate. Shared form tracks and repeated layout roles should use Flood owners. A semantic host shell can legitimately own positioning; record the actual reason.');
|
|
24
|
+
}
|
|
25
|
+
for(const match of source.matchAll(/\bmin-(?:width|inline-size)\s*:\s*(\d*\.?\d+)rem\b/g)){
|
|
26
|
+
if(Number(match[1])>0)record(match,'composition.scaled-minimum','Container / local ScrollArea','Rem-based minimum width candidate. If applied to the whole host, it can force page overflow when text grows. Dense table minima can be legitimate inside a local scroll owner; inspect the actual selector and enlarged desktop result.');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return {schemaVersion:1,issues,truncated,notChecked:['Text matching is not parsing: comments, strings and examples can match; aliases, dynamic types and indirect calls can be missed. Review source context.','This function reads only the supplied text. It does not execute it, open files or inspect imports, component capability, rendered layout or editorial necessity.']};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Read-only DOM observations. Pass this self-contained function to page.evaluate. */
|
|
33
|
+
export function inspectRenderedUI(options = {}) {
|
|
34
|
+
if (typeof document === 'undefined') throw new Error('inspectRenderedUI must run in the rendered browser document');
|
|
35
|
+
const root = options.rootSelector ? document.querySelector(options.rootSelector) : document;
|
|
36
|
+
if (!root) throw new Error('Flood inspection root was not found');
|
|
37
|
+
const limit = options.maxElements ?? 500;
|
|
38
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 2000) throw new Error('maxElements must be an integer from 1 to 2000');
|
|
39
|
+
const issues = [], copy = [], fields = [];
|
|
40
|
+
const visible = element => {
|
|
41
|
+
const style = getComputedStyle(element), box = element.getBoundingClientRect();
|
|
42
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && box.width > 0 && box.height > 0;
|
|
43
|
+
};
|
|
44
|
+
const label = element => (element.getAttribute('aria-label') || element.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 180);
|
|
45
|
+
const target = element => ({tag:element.tagName.toLowerCase(), id:element.id || null, className:element.getAttribute('class') || '', label:label(element)});
|
|
46
|
+
let truncated = false;
|
|
47
|
+
const addIssue = finding => { if (issues.length < limit) issues.push(finding); else truncated = true; };
|
|
48
|
+
const select = selector => {
|
|
49
|
+
const matches = [...root.querySelectorAll(selector)].filter(visible);
|
|
50
|
+
if(matches.length > limit) truncated = true;
|
|
51
|
+
return matches.slice(0, limit);
|
|
52
|
+
};
|
|
53
|
+
if(root===document&&document.documentElement.scrollWidth>innerWidth+1)addIssue({id:'layout.page-overflow',target:{tag:'html',id:document.documentElement.id||null,className:document.documentElement.getAttribute('class')||'',label:'Document'},offset:document.documentElement.scrollWidth-innerWidth,message:'The document extends beyond the viewport. Review the actual overflow owner at this text size; dense content can scroll locally. Hiding page overflow does not repair the layout.'});
|
|
54
|
+
for (const button of select('button.flood-button, button.flood-icon-button')) {
|
|
55
|
+
// Text is inspected outside real icon slots, SVGs and visually hidden names.
|
|
56
|
+
const walker = document.createTreeWalker(button, NodeFilter.SHOW_TEXT);
|
|
57
|
+
const parts = []; let node;
|
|
58
|
+
while ((node = walker.nextNode())) {
|
|
59
|
+
if (!node.parentElement?.closest('.flood-icon,svg,.flood-visually-hidden,[aria-hidden="true"]')) parts.push(node.textContent);
|
|
60
|
+
}
|
|
61
|
+
const text = parts.join('').trim();
|
|
62
|
+
if (/^[++×✕✖↗→←✓✔☰](?:\s|$)|(?:\s)[++×✕✖↗→←✓✔☰]$/u.test(text)) {
|
|
63
|
+
addIssue({id:'icons.text-glyph', target:target(button), message:'Possible action icon encoded as font text; review whether IconSlot with explicit artwork is required.'});
|
|
64
|
+
}
|
|
65
|
+
for (const svg of button.querySelectorAll('svg')) {
|
|
66
|
+
if (!svg.closest('.flood-icon,.flood-loading,.flood-spinner')) addIssue({id:'icons.unslotted-svg',target:target(button),message:'Action SVG has no Flood IconSlot owner; inspect sizing and accessible decoration.'});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const row of select('.flood-table tbody tr')) {
|
|
70
|
+
const controls = [...row.querySelectorAll('input:not([type="hidden"]),textarea,select')].filter(visible);
|
|
71
|
+
const actions = [...row.querySelectorAll('button,a.flood-link--button')].filter(visible);
|
|
72
|
+
if (!controls.length && !actions.length) continue;
|
|
73
|
+
const centres = controls.map(control=>{const r=control.getBoundingClientRect();return r.y+r.height/2;});
|
|
74
|
+
if (centres.length && Math.max(...centres)-Math.min(...centres)>3) continue;
|
|
75
|
+
const rowBox=row.getBoundingClientRect();
|
|
76
|
+
const expected = centres.length ? centres.reduce((a,b)=>a+b,0)/centres.length : rowBox.y+rowBox.height/2;
|
|
77
|
+
for (const cell of row.children) {
|
|
78
|
+
if(!cell.matches('td,th') || !visible(cell)) continue;
|
|
79
|
+
// Editable rows compare their passive content to input centres. Record/action
|
|
80
|
+
// rows compare every complete content block, including multiline identities.
|
|
81
|
+
if(centres.length && cell.querySelector('input,textarea,select')) continue;
|
|
82
|
+
const boxes=[];
|
|
83
|
+
const walker=document.createTreeWalker(cell,NodeFilter.SHOW_TEXT);let textNode;
|
|
84
|
+
while((textNode=walker.nextNode())) {
|
|
85
|
+
if(!textNode.textContent?.trim() || !textNode.parentElement || !visible(textNode.parentElement)
|
|
86
|
+
|| textNode.parentElement.closest('.flood-visually-hidden,[aria-hidden="true"],script,style')) continue;
|
|
87
|
+
const range=document.createRange();range.selectNodeContents(textNode);
|
|
88
|
+
boxes.push(...[...range.getClientRects()].filter(rect=>rect.width>0&&rect.height>0));
|
|
89
|
+
}
|
|
90
|
+
for(const control of [...cell.querySelectorAll('button,a.flood-link--button')].filter(visible)) boxes.push(control.getBoundingClientRect());
|
|
91
|
+
if(!boxes.length) continue;
|
|
92
|
+
const actual=(Math.min(...boxes.map(rect=>rect.top))+Math.max(...boxes.map(rect=>rect.bottom)))/2;
|
|
93
|
+
if(Math.abs(actual-expected)>3) addIssue({id:'table.mixed-row-centre',target:target(cell),offset:Math.round((actual-expected)*100)/100,message:centres.length
|
|
94
|
+
? 'Cell content is offset from neighbouring input centres. Review resolved geometry.'
|
|
95
|
+
: 'Cell content is offset from the centre of a record/action row. Compare multiline identity, values and actions; use deliberate top alignment only when justified.'});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const actionNames=select('button.flood-button,a.flood-link--button').map(button=>(button.textContent||'').trim().replace(/\s+/g,' ').toLowerCase()).filter(name=>name.length>=4);
|
|
99
|
+
let wordsChecked=0;
|
|
100
|
+
const segmenters=new Map();
|
|
101
|
+
const canInspectWords=typeof Intl.Segmenter==='function';
|
|
102
|
+
if(canInspectWords)for(const cell of select('.flood-table th,.flood-table td')){
|
|
103
|
+
if(cell.matches('[data-wrap="anywhere"]'))continue;
|
|
104
|
+
const locale=cell.closest('[lang]')?.getAttribute('lang')||document.documentElement.lang||undefined;
|
|
105
|
+
if(!segmenters.has(locale)){
|
|
106
|
+
try{segmenters.set(locale,new Intl.Segmenter(locale,{granularity:'word'}));}
|
|
107
|
+
catch{segmenters.set(locale,new Intl.Segmenter(undefined,{granularity:'word'}));}
|
|
108
|
+
}
|
|
109
|
+
const walker=document.createTreeWalker(cell,NodeFilter.SHOW_TEXT);let node;
|
|
110
|
+
while((node=walker.nextNode())){
|
|
111
|
+
if(!node.parentElement||!visible(node.parentElement)||node.parentElement.closest('code,pre,.flood-inline-code,.flood-visually-hidden,[aria-hidden="true"]'))continue;
|
|
112
|
+
for(const word of segmenters.get(locale).segment(node.textContent||'')){
|
|
113
|
+
if(!word.isWordLike||word.segment.length<4)continue;
|
|
114
|
+
if(wordsChecked>=limit){truncated=true;break;}wordsChecked++;
|
|
115
|
+
if(word.segment.length>200)continue;
|
|
116
|
+
const range=document.createRange();range.setStart(node,word.index);range.setEnd(node,word.index+word.segment.length);
|
|
117
|
+
const boxes=[...range.getClientRects()].filter(rect=>rect.width>0&&rect.height>0);
|
|
118
|
+
if(boxes.length>1&&Math.max(...boxes.map(rect=>rect.y))-Math.min(...boxes.map(rect=>rect.y))>2){
|
|
119
|
+
addIssue({id:'table.word-fragment',target:target(cell),word:word.segment,message:'A word is split across rendered lines in a table cell. Review intrinsic column width and wrapping; deliberate technical or localized line breaking can be an exception.'});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if(wordsChecked>=limit)break;
|
|
123
|
+
}
|
|
124
|
+
if(wordsChecked>=limit){truncated=true;break;}
|
|
125
|
+
}
|
|
126
|
+
const grids=select('form [class]').filter(element=>{
|
|
127
|
+
if(element.closest('table') || getComputedStyle(element).display!=='grid')return false;
|
|
128
|
+
return [...element.children].filter(visible).length>=2 && element.querySelectorAll('input,select,textarea,[role="combobox"]').length>=2;
|
|
129
|
+
}).map(element=>{
|
|
130
|
+
const style=getComputedStyle(element),columns=style.gridTemplateColumns.match(/[\d.]+px/g)?.map(parseFloat)??[];
|
|
131
|
+
return {element,columns,gap:parseFloat(style.columnGap)||0,rect:element.getBoundingClientRect()};
|
|
132
|
+
}).filter(grid=>grid.columns.length>=2);
|
|
133
|
+
const contentTracks=grid=>{
|
|
134
|
+
const owners='.flood-field,.flood-date-picker,.flood-date-field,.flood-time-field,.flood-date-range-field,.flood-date-time-picker';
|
|
135
|
+
const nodes=[...grid.element.querySelectorAll(owners)].filter(element=>visible(element)&&!element.closest('table')&&!element.parentElement?.closest(owners));
|
|
136
|
+
if(nodes.length>limit)truncated=true;
|
|
137
|
+
const tracks=[];
|
|
138
|
+
for(const element of nodes.slice(0,limit)){
|
|
139
|
+
const rect=element.getBoundingClientRect();
|
|
140
|
+
const track=tracks.find(item=>Math.abs(item.x-rect.x)<=1);
|
|
141
|
+
if(track){if(Math.abs(track.width-rect.width)>1)return null;}
|
|
142
|
+
else tracks.push({x:rect.x,width:rect.width});
|
|
143
|
+
}
|
|
144
|
+
if(tracks.length!==grid.columns.length)return null;
|
|
145
|
+
tracks.sort((a,b)=>a.x-b.x);
|
|
146
|
+
return {tracks,left:tracks[0].x,right:Math.max(...tracks.map(track=>track.x+track.width))};
|
|
147
|
+
};
|
|
148
|
+
for(const grid of grids)grid.content=contentTracks(grid);
|
|
149
|
+
for(let i=0;i<grids.length;i++)for(let j=i+1;j<grids.length;j++){
|
|
150
|
+
const a=grids[i],b=grids[j];
|
|
151
|
+
if(a.element.closest('form')!==b.element.closest('form'))continue;
|
|
152
|
+
if(a.element.contains(b.element)||b.element.contains(a.element))continue;
|
|
153
|
+
if(!(a.rect.bottom<=b.rect.top+1||b.rect.bottom<=a.rect.top+1))continue;
|
|
154
|
+
if(a.content&&b.content&&a.content.tracks.length===b.content.tracks.length&&Math.abs(a.content.left-b.content.left)<=1&&Math.abs(a.content.right-b.content.right)<=1){
|
|
155
|
+
const offset=Math.max(...a.content.tracks.map((track,index)=>Math.max(Math.abs(track.x-b.content.tracks[index].x),Math.abs(track.width-b.content.tracks[index].width))));
|
|
156
|
+
if(offset>1)addIssue({id:'forms.content-track-drift',target:target(b.element),related:target(a.element),offset:Math.round(offset*100)/100,message:'Stacked form groups share content edges but their actual field starts or widths differ. Review nested surface padding and shared track ownership; matching Grid props or outer boxes is insufficient.'});
|
|
157
|
+
}
|
|
158
|
+
if(Math.abs(a.rect.x-b.rect.x)>1||Math.abs(a.rect.width-b.rect.width)>1)continue;
|
|
159
|
+
const starts=grid=>grid.columns.map((_,index)=>grid.columns.slice(0,index).reduce((sum,width)=>sum+width,0)+index*grid.gap);
|
|
160
|
+
const left=starts(a),right=starts(b);
|
|
161
|
+
const drift=a.columns.length===b.columns.length?Math.max(...left.map((x,index)=>Math.abs(x-right[index]))):null;
|
|
162
|
+
if(drift===null||drift>1)addIssue({id:'forms.track-drift',target:target(b.element),related:target(a.element),...(drift===null?{}:{offset:Math.round(drift*100)/100}),message:'Stacked form grids share an outer frame but use different tracks. Review common gutters/group hierarchy or document why these contexts need different columns.'});
|
|
163
|
+
}
|
|
164
|
+
for(const element of select('h1,h2,h3,h4,h5,h6,.flood-type--helper,.flood-type--compact')) {
|
|
165
|
+
if(label(element)) {
|
|
166
|
+
copy.push(target(element));
|
|
167
|
+
if(!element.closest('button,a,label') && !element.matches('h1,h2,h3,h4,h5,h6')) {
|
|
168
|
+
if(!element.closest('.flood-field,.flood-fieldset,.flood-table,.flood-metric,.flood-status-indicator,.flood-file-meta,.flood-description-list,nav,article,dialog,[role="document"],[role="status"],[role="alert"],[role="alertdialog"],[role="dialog"]')) {
|
|
169
|
+
addIssue({id:'copy.unowned-support',target:target(element),message:'Supporting text sits outside a field, operation state, navigation or structured document. Identify the exact current decision it serves; remove implementation narration. Do not change typography or add a semantic wrapper merely to silence this observation.'});
|
|
170
|
+
}
|
|
171
|
+
const text=(element.textContent||'').trim().replace(/\s+/g,' ').toLowerCase();
|
|
172
|
+
if(actionNames.some(name=>text.includes(name))) addIssue({id:'copy.action-echo',target:target(element),message:'Supporting copy repeats a currently visible action label. Review whether it adds a necessary constraint or only narrates the control.'});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
for(const element of select('.flood-field,fieldset,.flood-section')) {
|
|
177
|
+
const rect=element.getBoundingClientRect();
|
|
178
|
+
fields.push({...target(element),x:rect.x,y:rect.y,width:rect.width,height:rect.height});
|
|
179
|
+
}
|
|
180
|
+
return {schemaVersion:1, issues, copy, fields, truncated, notChecked:[
|
|
181
|
+
'Editorial necessity and accuracy require contextual review; copy inventory is not an editorial verdict.',
|
|
182
|
+
'Form grouping and column hierarchy require review of task relationships and visible geometry.',
|
|
183
|
+
'Only the currently rendered document state is inspected; portals outside a selected root, other pages, iframes and shadow roots are not traversed.',
|
|
184
|
+
'These observations do not certify accessibility, functional correctness or overall visual quality.',
|
|
185
|
+
...(!canInspectWords?['Word fragmentation was not inspected because Intl.Segmenter is unavailable.']:[])
|
|
186
|
+
]};
|
|
187
|
+
}
|
package/manifest.json
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"entry": "SKILL.md",
|
|
4
4
|
"files": [
|
|
5
|
+
{
|
|
6
|
+
"source": "dist/catalog.json",
|
|
7
|
+
"target": "flood/catalog.json",
|
|
8
|
+
"kind": "catalog"
|
|
9
|
+
},
|
|
5
10
|
{
|
|
6
11
|
"source": "DESIGN.md",
|
|
7
12
|
"target": "flood/DESIGN.md",
|
|
@@ -67,6 +72,11 @@
|
|
|
67
72
|
"target": ".agents/skills/flood-ui/references/flood-spacing/SKILL.md",
|
|
68
73
|
"kind": "skill"
|
|
69
74
|
},
|
|
75
|
+
{
|
|
76
|
+
"source": "references/flood-motion/SKILL.md",
|
|
77
|
+
"target": ".agents/skills/flood-ui/references/flood-motion/SKILL.md",
|
|
78
|
+
"kind": "skill"
|
|
79
|
+
},
|
|
70
80
|
{
|
|
71
81
|
"source": "references/flood-foundations/SKILL.md",
|
|
72
82
|
"target": ".agents/skills/flood-ui/references/flood-foundations/SKILL.md",
|
|
@@ -106,9 +116,49 @@
|
|
|
106
116
|
"source": "references/flood-verification/SKILL.md",
|
|
107
117
|
"target": ".agents/skills/flood-ui/references/flood-verification/SKILL.md",
|
|
108
118
|
"kind": "skill"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"source": "references/knowledge.md",
|
|
122
|
+
"target": ".agents/skills/flood-ui/references/knowledge.md",
|
|
123
|
+
"kind": "reference"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"source": "references/flood-interface-content/SKILL.md",
|
|
127
|
+
"target": ".agents/skills/flood-ui/references/flood-interface-content/SKILL.md",
|
|
128
|
+
"kind": "skill"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"source": "references/interface-content-examples.md",
|
|
132
|
+
"target": ".agents/skills/flood-ui/references/interface-content-examples.md",
|
|
133
|
+
"kind": "reference"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"source": "references/rendered-review.md",
|
|
137
|
+
"target": ".agents/skills/flood-ui/references/rendered-review.md",
|
|
138
|
+
"kind": "reference"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"source": "references/flood-hierarchy/SKILL.md",
|
|
142
|
+
"target": ".agents/skills/flood-ui/references/flood-hierarchy/SKILL.md",
|
|
143
|
+
"kind": "skill"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"source": "references/flood-records/SKILL.md",
|
|
147
|
+
"target": ".agents/skills/flood-ui/references/flood-records/SKILL.md",
|
|
148
|
+
"kind": "skill"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"source": "references/record-decisions.md",
|
|
152
|
+
"target": ".agents/skills/flood-ui/references/record-decisions.md",
|
|
153
|
+
"kind": "reference"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"source": "references/component-families.md",
|
|
157
|
+
"target": ".agents/skills/flood-ui/references/component-families.md",
|
|
158
|
+
"kind": "reference"
|
|
109
159
|
}
|
|
110
160
|
],
|
|
111
|
-
"version": "0.2.
|
|
161
|
+
"version": "0.2.11",
|
|
112
162
|
"installedEntry": ".agents/skills/flood-ui/SKILL.md",
|
|
113
163
|
"pathBase": "source is package-relative; target is relative to the host project root"
|
|
114
164
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flood-md/agent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Versioned Flood design instructions for coding agents.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,12 +13,24 @@
|
|
|
13
13
|
"DESIGN.md",
|
|
14
14
|
"SKILL.md",
|
|
15
15
|
"references",
|
|
16
|
+
"lib",
|
|
17
|
+
"dist",
|
|
18
|
+
"knowledge.json",
|
|
16
19
|
"workflows.json",
|
|
17
20
|
"rules.json",
|
|
18
21
|
"manifest.json",
|
|
19
22
|
"README.md"
|
|
20
23
|
],
|
|
21
24
|
"exports": {
|
|
25
|
+
"./inspect": {
|
|
26
|
+
"types": "./lib/inspect.d.ts",
|
|
27
|
+
"import": "./lib/inspect.mjs"
|
|
28
|
+
},
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./lib/index.d.ts",
|
|
31
|
+
"import": "./lib/index.mjs"
|
|
32
|
+
},
|
|
33
|
+
"./dist/*": "./dist/*",
|
|
22
34
|
"./DESIGN.md": "./DESIGN.md",
|
|
23
35
|
"./SKILL.md": "./SKILL.md",
|
|
24
36
|
"./manifest.json": "./manifest.json",
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Author a component that belongs to Flood
|
|
2
|
+
|
|
3
|
+
Use when adding a reusable primitive, extending its visual contract or composing
|
|
4
|
+
a new shared component. This applies inside Flood itself and to project-owned
|
|
5
|
+
composites. Keep the installed/source distinction: a consumer cannot invent a new
|
|
6
|
+
package export or edit an installed dependency to implement these decisions.
|
|
7
|
+
|
|
8
|
+
## Choose the family before choosing values
|
|
9
|
+
|
|
10
|
+
Identify the component's role and compare the closest existing owners in their
|
|
11
|
+
rendered states. A new name is not a reason for a new visual recipe. Record the
|
|
12
|
+
chosen siblings and intentional differences in development notes, outside the UI.
|
|
13
|
+
|
|
14
|
+
| Relationship | What should be shared |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| Action and icon action | Button/IconButton height, text, icon scale, insets and focus treatment |
|
|
17
|
+
| Text/selection/number field | Field label relationship, control height and field-state semantics |
|
|
18
|
+
| Command collection | Toolbar/Menu owners, action sizes and keyboard ownership |
|
|
19
|
+
| Floating surface | Existing overlay material, layer placement, inset and focus restoration |
|
|
20
|
+
| Named content or record | Typography, hierarchy and metadata owners, not a new title scale |
|
|
21
|
+
|
|
22
|
+
Reuse these owners inside the new component. Extend the smallest missing owner
|
|
23
|
+
before adding a composite override. For example, the Toolbar that draws an
|
|
24
|
+
OverflowActions background owns its shape; a playground-only border-radius does
|
|
25
|
+
not establish that capability for other consumers.
|
|
26
|
+
|
|
27
|
+
## Define the visual and behavioral contract together
|
|
28
|
+
|
|
29
|
+
- Use matching semantic token roles. Equal-looking values are not necessarily
|
|
30
|
+
interchangeable roles; a field radius and an action radius can evolve separately.
|
|
31
|
+
- Derive sizes from the family's controls. Label line height, explicit icon box,
|
|
32
|
+
interactive height and optical insets must still agree at every exposed size.
|
|
33
|
+
- Name variants by a meaningful role or shape. Keep appearance separate from
|
|
34
|
+
state: selected, read-only, disabled, invalid and focus are not interchangeable.
|
|
35
|
+
- Make the outer geometry deliberate. A single-row capsule action rail can use
|
|
36
|
+
a pill shape; a wrapping or vertical group may need rounded geometry. Do not
|
|
37
|
+
blindly apply a pill to a multi-row surface. Use the spacing workflow to assess
|
|
38
|
+
nested radii and the actual inset; independently interactive neighbours need
|
|
39
|
+
not share a concentric-radius formula.
|
|
40
|
+
- Account for padding and stroke in the space available to children. Overflow
|
|
41
|
+
calculations use the content box, not the outer border rectangle. Check the
|
|
42
|
+
threshold at which an item moves into the overflow owner.
|
|
43
|
+
- Keep behaviour in the component that owns it. A focusable read-only text field
|
|
44
|
+
still needs a visible focus indicator and selection/copy. It blocks edits and
|
|
45
|
+
its chooser; disabled has a different native contract. Do not erase focus to
|
|
46
|
+
make a state look quieter or add helper copy merely to explain that contract.
|
|
47
|
+
|
|
48
|
+
## Compare the family in one scene
|
|
49
|
+
|
|
50
|
+
Repeated navigation/record rows share columns, not just height and font. Before
|
|
51
|
+
accepting a composition, compare glyph centers, label starts and trailing action
|
|
52
|
+
centers between header, groups, iconless/icon-bearing rows and footer. Define the
|
|
53
|
+
indent of descendants deliberately and reserve missing peer anatomy. Scrollbar
|
|
54
|
+
visibility must not silently change only the middle pane's content tracks.
|
|
55
|
+
|
|
56
|
+
NavigationItem owns shared row geometry for native Link destinations, Button
|
|
57
|
+
commands and disclosure. A branch without a destination uses a full-row toggle;
|
|
58
|
+
a destination with children has distinct sibling link and disclosure targets,
|
|
59
|
+
never nested controls or a link click that also toggles. The complete row owns
|
|
60
|
+
its current/hover surface, so selection does not end at an unrelated shorter pill
|
|
61
|
+
before the chevron. Budget real trailing anatomy once. NavigationTree composes
|
|
62
|
+
these rows and manages hierarchy rather than supplying a parallel visual recipe.
|
|
63
|
+
|
|
64
|
+
NavigationGroup owns optional label/action/separator and independent group
|
|
65
|
+
disclosure. Its compact presentation reveals destinations without discarding the
|
|
66
|
+
accepted expanded choice. Hiding a title cannot remove a needed accessible name
|
|
67
|
+
or make a command unreachable. Compare headed/unheaded groups and all optional
|
|
68
|
+
anatomy; an available slot is not a reason to populate it.
|
|
69
|
+
|
|
70
|
+
Sidebar reuses NavigationGroup and NavigationTree. Its compact branches reuse
|
|
71
|
+
Popover; full destinations retain Link and normal Tab behavior. Header/footer
|
|
72
|
+
adapt through slots, with opt-in shell separators. The actual brand, toggle,
|
|
73
|
+
destination and account hit areas share the full compact rail axis; a centered
|
|
74
|
+
wrapper around a start-aligned Menu trigger is insufficient. Expanded slots share
|
|
75
|
+
icon/text tracks. Its ScrollArea uses the existing edge lane for clearance. Keep
|
|
76
|
+
material at the sidebar owner, without another material behind each group.
|
|
77
|
+
Separate route identity, group/branch expansion and sidebar width; compare both
|
|
78
|
+
profiles beside the small owners at the same size, with long text and bounded
|
|
79
|
+
height. The 0.2.11 candidate's APIs require matching installed declarations.
|
|
80
|
+
Disclosure artwork stays subordinate to destination icons without reducing hit
|
|
81
|
+
areas; IconButton.iconSize separates these responsibilities. Compact hints reuse
|
|
82
|
+
Tooltip on the actual Link/button, preserve refs and event handlers, and disappear
|
|
83
|
+
when a branch flyout opens. Pane-toggle glyphs change with accepted visibility.
|
|
84
|
+
|
|
85
|
+
Material context is part of the family contract. Compare actions, fields,
|
|
86
|
+
choices, selection and supporting elements on the same material. A shared
|
|
87
|
+
semantic context should correct the family together; a descendant-only button
|
|
88
|
+
override leaves fields and composed controls inconsistent. Keep absolute
|
|
89
|
+
material colors separate from contextual control roles and verify the nearest
|
|
90
|
+
nested owner resets those roles. Preserve accent focus and destructive meaning.
|
|
91
|
+
|
|
92
|
+
BulkActionBar is a one-row capsule for a small set of selection commands. Keep
|
|
93
|
+
one to three visible commands, prefer two, and disclose the remainder through
|
|
94
|
+
its overflow owner. Do not wrap this floating rail into a second row or use it
|
|
95
|
+
as another general toolbar. The host owns selected IDs, confirmation, accepted
|
|
96
|
+
updates, positioning and scroll clearance.
|
|
97
|
+
BulkActionBar counter can be label, number or none. Visible counters reserve
|
|
98
|
+
their width across count changes; choose counterWidth for the expected localized
|
|
99
|
+
summary rather than sizing it from each current count. Hiding it removes its slot.
|
|
100
|
+
|
|
101
|
+
CollectionToolbar composes the search, Tag, Button and Popover families without
|
|
102
|
+
adding a surface. Use normal Tab navigation for this mixed input/action group.
|
|
103
|
+
Compare SearchInput and existing collection controls at the same controlSize;
|
|
104
|
+
preserve the end anchor, reserved reset space and contextual action colors.
|
|
105
|
+
Keep saved views and query execution in the host; filterContent supplies its draft.
|
|
106
|
+
|
|
107
|
+
CollectionViewSwitcher composes Select and existing action owners on a separate
|
|
108
|
+
saved-view row. One command uses Button; multiple commands use OverflowActions.
|
|
109
|
+
Use its plain container variant beside peer fields so another material/inset does
|
|
110
|
+
not enlarge the command group. Contained remains appropriate for standalone rails.
|
|
111
|
+
Use the same controlSize and keep Select and action hit areas coherent. Queries,
|
|
112
|
+
dirty-state policy, permissions, dialogs and persistence belong to the host.
|
|
113
|
+
|
|
114
|
+
FilterGroup composes FilterCondition and existing matching/actions. Preserve
|
|
115
|
+
stable row IDs and accepted controlled state. Addition/removal focus belongs to
|
|
116
|
+
the group after acceptance; Apply/Cancel, validation and query evaluation remain
|
|
117
|
+
host-owned. Do not add a nested material or empty-state narration just because
|
|
118
|
+
the flat group has its own component name.
|
|
119
|
+
|
|
120
|
+
Render the new control beside its nearest sibling, with the same label/content,
|
|
121
|
+
size, surface and root text scale. Review rest, hover, keyboard focus and the
|
|
122
|
+
states the component actually supports. Include long content, empty/optional
|
|
123
|
+
slots, narrow tracks and enlarged text. The scene is verification tooling, not a
|
|
124
|
+
new runtime wrapper or a mandatory product screen.
|
|
125
|
+
|
|
126
|
+
Measure what looked wrong: glyph/text centres, visible insets, nested curves,
|
|
127
|
+
focus bounds and overflow thresholds. Exercise resize while an action or popup
|
|
128
|
+
has focus. After a shared-owner correction, inspect another consumer. A green
|
|
129
|
+
build or token reuse alone does not establish visual kinship.
|
|
130
|
+
|
|
131
|
+
## Keep the contract discoverable
|
|
132
|
+
|
|
133
|
+
In Flood source, update declarations, component examples, applicable guidance
|
|
134
|
+
and the version decision together; regenerate the existing knowledge catalog.
|
|
135
|
+
In a host, document the project-owned composite and its actual dependencies.
|
|
136
|
+
Do not add unsupported props to examples or a parallel handwritten API index.
|
|
137
|
+
Report executed behavioural/visual checks separately from guidance that was
|
|
138
|
+
merely read. Record deliberate family exceptions instead of making an isolated
|
|
139
|
+
fix the next component's accidental default.
|
|
140
|
+
|
|
141
|
+
Track actual consumers and interactions for a proposed atom. Demonstrate a shared
|
|
142
|
+
responsibility in different compositions before promoting a recipe helper into
|
|
143
|
+
the package. Do not insert an owner merely to increase its usage count: one
|
|
144
|
+
command can remain Button while a constrained command collection uses
|
|
145
|
+
OverflowActions. Reuse is judged by correct roles and reachable behaviour, not
|
|
146
|
+
the length of an import list.
|
|
@@ -138,9 +138,9 @@ Link defaults to text; opt-in solid/subtle/outline/ghost and
|
|
|
138
138
|
Button sizes share the button recipe without changing native anchor semantics.
|
|
139
139
|
Use href/target for navigation, Button for actions; never nest the two or add
|
|
140
140
|
role=button. Link has no disabled/loading contract. Available in 0.2.3.
|
|
141
|
-
Tabs switches linked content panels
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
Tabs switches linked content panels. Use the default contained variant for a
|
|
142
|
+
shared surface where the selected fill joins the panel. Use variant="line" for
|
|
143
|
+
page-level sections whose content sits on the page canvas. Arrows activate by default,
|
|
144
144
|
or focus only with activationMode=manual; Enter/Space commit. One tab
|
|
145
145
|
stop, RTL/orientation aware, disabled skip. Panels stay mounted/hidden and retain
|
|
146
146
|
native form constraints. Controlled/missing/disabled selected values derive first
|
|
@@ -278,4 +278,4 @@ Section headingLevel controls native h2–h6 semantics. Its independent headingS
|
|
|
278
278
|
uses the existing Heading roles (section/page/large/display), defaulting to section.
|
|
279
279
|
Keep document hierarchy meaningful when changing visual emphasis.
|
|
280
280
|
|
|
281
|
-
|
|
281
|
+
Use CommandPalette for modal navigation and command search. The host owns commands, permissions, controlled query reset and shortcuts. Keep CommandList for inline search. The project recipe lives in docs/recipes/command-palette.md in the source repository.
|