@flood-md/agent 0.2.8 → 0.2.10
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 +26 -4
- package/README.md +40 -1
- package/SKILL.md +46 -4
- package/dist/catalog.json +7136 -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-aspect-ratio.md +36 -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-button-group.md +39 -0
- package/dist/materials/component-button.md +43 -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 +40 -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 +50 -0
- package/dist/materials/component-command-list.md +48 -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 +42 -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 +58 -0
- package/dist/materials/component-date-field.md +57 -0
- package/dist/materials/component-date-picker.md +66 -0
- package/dist/materials/component-date-range-field.md +62 -0
- package/dist/materials/component-date-range-picker.md +72 -0
- package/dist/materials/component-date-time-picker.md +60 -0
- package/dist/materials/component-date-time-range-picker.md +66 -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 +50 -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-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 +41 -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-meter.md +43 -0
- package/dist/materials/component-metric.md +39 -0
- package/dist/materials/component-multi-select.md +52 -0
- package/dist/materials/component-navigation-list.md +38 -0
- package/dist/materials/component-navigation-tree.md +43 -0
- package/dist/materials/component-number-input.md +47 -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 +47 -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 +51 -0
- package/dist/materials/component-select.md +45 -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 +43 -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 +39 -0
- package/dist/materials/component-toolbar.md +42 -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 +88 -0
- package/dist/materials/guide-agents.md +96 -0
- package/dist/materials/guide-faq.md +84 -0
- package/dist/materials/guide-foundations.md +232 -0
- package/dist/materials/guide-installation.md +79 -0
- package/dist/materials/guide-interface-content.md +76 -0
- package/dist/materials/guide-introduction.md +40 -0
- package/dist/materials/guide-layout.md +193 -0
- package/dist/materials/guide-privacy.md +23 -0
- package/dist/materials/guide-project-ui.md +127 -0
- package/dist/materials/guide-skills.md +108 -0
- package/dist/materials/guide-theming.md +73 -0
- package/dist/materials/recipe-command-palette.md +66 -0
- package/dist/materials/recipe-filter-bar.md +59 -0
- package/dist/materials/recipe-form-submission.md +39 -0
- package/dist/materials/recipe-grouped-form.md +66 -0
- package/dist/materials/recipe-select-all.md +31 -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.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.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.canvas.json +39 -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.overlay.json +39 -0
- package/dist/materials/token-sys.color.surface.raised.json +39 -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.duration-fast.json +34 -0
- package/dist/materials/token-sys.motion.duration-normal.json +34 -0
- package/dist/materials/token-sys.motion.duration-reduced.json +34 -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 +409 -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 +91 -1
- package/package.json +9 -1
- package/references/flood-accessibility/SKILL.md +40 -0
- package/references/flood-composition/SKILL.md +30 -0
- package/references/flood-data-display/SKILL.md +69 -0
- package/references/flood-feedback/SKILL.md +58 -0
- package/references/flood-forms/SKILL.md +94 -0
- package/references/flood-foundations/SKILL.md +44 -0
- package/references/flood-hierarchy/SKILL.md +87 -0
- package/references/flood-interface-content/SKILL.md +142 -0
- package/references/flood-navigation/SKILL.md +35 -0
- package/references/flood-overlays/SKILL.md +52 -0
- package/references/flood-records/SKILL.md +108 -0
- package/references/flood-spacing/SKILL.md +62 -0
- package/references/flood-verification/SKILL.md +99 -0
- package/references/interface-content-examples.md +47 -0
- package/references/knowledge.md +49 -0
- package/references/record-decisions.md +25 -0
- package/references/rendered-review.md +97 -0
- package/references/routing.md +1 -0
- package/rules.json +454 -2
- package/workflows.json +152 -0
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",
|
|
@@ -56,9 +61,94 @@
|
|
|
56
61
|
"source": "references/flood-composition/SKILL.md",
|
|
57
62
|
"target": ".agents/skills/flood-ui/references/flood-composition/SKILL.md",
|
|
58
63
|
"kind": "skill"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"source": "workflows.json",
|
|
67
|
+
"target": ".agents/skills/flood-ui/workflows.json",
|
|
68
|
+
"kind": "reference"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"source": "references/flood-spacing/SKILL.md",
|
|
72
|
+
"target": ".agents/skills/flood-ui/references/flood-spacing/SKILL.md",
|
|
73
|
+
"kind": "skill"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"source": "references/flood-foundations/SKILL.md",
|
|
77
|
+
"target": ".agents/skills/flood-ui/references/flood-foundations/SKILL.md",
|
|
78
|
+
"kind": "skill"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"source": "references/flood-accessibility/SKILL.md",
|
|
82
|
+
"target": ".agents/skills/flood-ui/references/flood-accessibility/SKILL.md",
|
|
83
|
+
"kind": "skill"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"source": "references/flood-forms/SKILL.md",
|
|
87
|
+
"target": ".agents/skills/flood-ui/references/flood-forms/SKILL.md",
|
|
88
|
+
"kind": "skill"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"source": "references/flood-overlays/SKILL.md",
|
|
92
|
+
"target": ".agents/skills/flood-ui/references/flood-overlays/SKILL.md",
|
|
93
|
+
"kind": "skill"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"source": "references/flood-navigation/SKILL.md",
|
|
97
|
+
"target": ".agents/skills/flood-ui/references/flood-navigation/SKILL.md",
|
|
98
|
+
"kind": "skill"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"source": "references/flood-data-display/SKILL.md",
|
|
102
|
+
"target": ".agents/skills/flood-ui/references/flood-data-display/SKILL.md",
|
|
103
|
+
"kind": "skill"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"source": "references/flood-feedback/SKILL.md",
|
|
107
|
+
"target": ".agents/skills/flood-ui/references/flood-feedback/SKILL.md",
|
|
108
|
+
"kind": "skill"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"source": "references/flood-verification/SKILL.md",
|
|
112
|
+
"target": ".agents/skills/flood-ui/references/flood-verification/SKILL.md",
|
|
113
|
+
"kind": "skill"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"source": "references/knowledge.md",
|
|
117
|
+
"target": ".agents/skills/flood-ui/references/knowledge.md",
|
|
118
|
+
"kind": "reference"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"source": "references/flood-interface-content/SKILL.md",
|
|
122
|
+
"target": ".agents/skills/flood-ui/references/flood-interface-content/SKILL.md",
|
|
123
|
+
"kind": "skill"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"source": "references/interface-content-examples.md",
|
|
127
|
+
"target": ".agents/skills/flood-ui/references/interface-content-examples.md",
|
|
128
|
+
"kind": "reference"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"source": "references/rendered-review.md",
|
|
132
|
+
"target": ".agents/skills/flood-ui/references/rendered-review.md",
|
|
133
|
+
"kind": "reference"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"source": "references/flood-hierarchy/SKILL.md",
|
|
137
|
+
"target": ".agents/skills/flood-ui/references/flood-hierarchy/SKILL.md",
|
|
138
|
+
"kind": "skill"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"source": "references/flood-records/SKILL.md",
|
|
142
|
+
"target": ".agents/skills/flood-ui/references/flood-records/SKILL.md",
|
|
143
|
+
"kind": "skill"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"source": "references/record-decisions.md",
|
|
147
|
+
"target": ".agents/skills/flood-ui/references/record-decisions.md",
|
|
148
|
+
"kind": "reference"
|
|
59
149
|
}
|
|
60
150
|
],
|
|
61
|
-
"version": "0.2.
|
|
151
|
+
"version": "0.2.10",
|
|
62
152
|
"installedEntry": ".agents/skills/flood-ui/SKILL.md",
|
|
63
153
|
"pathBase": "source is package-relative; target is relative to the host project root"
|
|
64
154
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flood-md/agent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Versioned Flood design instructions for coding agents.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,15 +13,23 @@
|
|
|
13
13
|
"DESIGN.md",
|
|
14
14
|
"SKILL.md",
|
|
15
15
|
"references",
|
|
16
|
+
"lib",
|
|
17
|
+
"dist",
|
|
18
|
+
"knowledge.json",
|
|
19
|
+
"workflows.json",
|
|
16
20
|
"rules.json",
|
|
17
21
|
"manifest.json",
|
|
18
22
|
"README.md"
|
|
19
23
|
],
|
|
20
24
|
"exports": {
|
|
25
|
+
"./inspect": {"types":"./lib/inspect.d.ts","import":"./lib/inspect.mjs"},
|
|
26
|
+
".": {"types":"./lib/index.d.ts","import":"./lib/index.mjs"},
|
|
27
|
+
"./dist/*": "./dist/*",
|
|
21
28
|
"./DESIGN.md": "./DESIGN.md",
|
|
22
29
|
"./SKILL.md": "./SKILL.md",
|
|
23
30
|
"./manifest.json": "./manifest.json",
|
|
24
31
|
"./rules.json": "./rules.json",
|
|
32
|
+
"./workflows.json": "./workflows.json",
|
|
25
33
|
"./references/*": "./references/*"
|
|
26
34
|
},
|
|
27
35
|
"publishConfig": {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flood-accessibility
|
|
3
|
+
description: Review Flood interface names, keyboard flow, focus, perception and reflow in the actual host. Use for interaction or accessibility acceptance.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Review an actual task
|
|
7
|
+
|
|
8
|
+
Use [accessibility](../accessibility.md) and the installed component's contract.
|
|
9
|
+
A scanner result is supporting evidence, not certification of an application.
|
|
10
|
+
|
|
11
|
+
## Trace the task from entry to completion
|
|
12
|
+
|
|
13
|
+
- Identify the name, role, state and instruction for each actionable control.
|
|
14
|
+
Retain native semantics before adding ARIA. A placeholder, color or tooltip
|
|
15
|
+
alone is not an adequate field name or required instruction.
|
|
16
|
+
- If a visible label is intentionally hidden, preserve the supplied label/legend
|
|
17
|
+
through the supported API and verify its contextual meaning and focus reveal.
|
|
18
|
+
Do not remove the label or invent unsupported hideLabel props on bare controls.
|
|
19
|
+
- Complete the task with keyboard only, including reverse navigation, cancel,
|
|
20
|
+
invalid submission and recovery. Distinguish roving focus, selection and
|
|
21
|
+
activation; arrow behavior follows the specific component contract.
|
|
22
|
+
- After pointer activation of a custom keyboard-owning control, inspect actual
|
|
23
|
+
focus before testing keys. Some browsers do not mouse-focus native buttons;
|
|
24
|
+
a custom combobox cannot rely on that default. Preserve an intentional focus
|
|
25
|
+
move made by the host callback rather than forcing focus back afterward.
|
|
26
|
+
- Check the accent focus perimeter in combinations with invalid, selected and
|
|
27
|
+
read-only states. It must remain visible without changing layout or hiding
|
|
28
|
+
beneath a rail, sticky region or overlay. Closing an overlay returns focus
|
|
29
|
+
appropriately without overriding a deliberate host focus move.
|
|
30
|
+
- Evaluate text with its actual size, weight and composited background using
|
|
31
|
+
applicable WCAG checks and supplementary APCA guidance. Decorative lines are
|
|
32
|
+
different from necessary control/state indicators; do not relabel a necessary
|
|
33
|
+
cue as decoration to avoid checking it. APCA is not WCAG conformance.
|
|
34
|
+
- Exercise narrow width, root text enlargement, long content, RTL where supported,
|
|
35
|
+
forced colors and the existing reduced-motion fallback. These checks do not
|
|
36
|
+
introduce new animation or theme requirements.
|
|
37
|
+
|
|
38
|
+
For assistive-technology announcements, inspect live-region semantics and test
|
|
39
|
+
with the actual technology when available. Do not report audible announcements
|
|
40
|
+
from a DOM role alone. Record any device/technology that was not tested.
|
|
@@ -10,6 +10,11 @@ components. Preserve their anatomy and behavior when improving a composition.
|
|
|
10
10
|
Read the governing project instructions, matching declarations and relevant
|
|
11
11
|
component guide first. This skill grants no additional permissions.
|
|
12
12
|
|
|
13
|
+
When introducing visible interface text, apply the
|
|
14
|
+
[interface content skill](../flood-interface-content/SKILL.md) before filling
|
|
15
|
+
optional headings, descriptions or helper slots. Existing user-authored content
|
|
16
|
+
is not permission to rewrite it during an unrelated layout correction.
|
|
17
|
+
|
|
13
18
|
## Choose the smallest existing owner
|
|
14
19
|
|
|
15
20
|
Before changing JSX, identify the visible roles and their existing owners:
|
|
@@ -18,6 +23,31 @@ metadata, IconButton for an icon action, Dialog for modal behavior, Text/Heading
|
|
|
18
23
|
for typography, and the appropriate form, list or table primitive for semantics.
|
|
19
24
|
Check the actual installed exports and props; do not invent APIs from newer docs.
|
|
20
25
|
|
|
26
|
+
Search the installed catalog by the role before settling on a low-level building
|
|
27
|
+
block. Read the selected contract and compare a more specific owner when one
|
|
28
|
+
exists: Metric for a labelled statistic; DatePicker for ordinary date selection
|
|
29
|
+
with typing and a calendar, DateField for an explicitly entry-only interaction;
|
|
30
|
+
StatusIndicator for a named state. Input type="date"
|
|
31
|
+
invokes the browser's picker, not Flood's Calendar. Use it only for an explicit
|
|
32
|
+
native-picker requirement or a documented limitation of the installed controls.
|
|
33
|
+
Heading/Text remain valid for ordinary prose, not a default replacement for an
|
|
34
|
+
available statistic component.
|
|
35
|
+
|
|
36
|
+
Include layout and secondary interactions in that selection: Grid for repeated
|
|
37
|
+
form tracks, Stack/Inline for related layout, AlertDialog for a discard/delete
|
|
38
|
+
confirmation, and Alert/StatusIndicator for the appropriate feedback role.
|
|
39
|
+
window.confirm/alert/prompt are browser UI, not Flood components. A small scope
|
|
40
|
+
does not justify replacing an available owner with native UI or a parallel CSS
|
|
41
|
+
grid. Keep native form, table and semantic document/shell elements where they
|
|
42
|
+
supply structure; styling the host shell is different from reimplementing a
|
|
43
|
+
provided layout or control. Document a genuine missing capability before using
|
|
44
|
+
a project-owned adapter.
|
|
45
|
+
|
|
46
|
+
Record the selected owner and any rejected match before implementing the surface.
|
|
47
|
+
If discovery finds no suitable component, state the missing capability; do not
|
|
48
|
+
claim that the library has no owner without checking. Keep this decision record
|
|
49
|
+
in development notes, never as extra explanatory text in the product.
|
|
50
|
+
|
|
21
51
|
- Reuse the matching component with its supported props and tokens.
|
|
22
52
|
- If spacing or alignment is wrong, inspect both the primitive and the parent
|
|
23
53
|
composition. Fix the owner of the relationship. Keep the primitive intact.
|