@nyuchi/mzizi-mcp 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -57
- package/dist/generated/registry-pin.d.ts +1 -1
- package/dist/generated/registry-pin.js +1 -1
- package/dist/generated/registry.d.ts +12 -2
- package/dist/generated/registry.d.ts.map +1 -1
- package/dist/generated/registry.js +69 -3
- package/dist/generated/registry.js.map +1 -1
- package/dist/generated/skills.js +1 -1
- package/dist/generated/skills.js.map +1 -1
- package/dist/registry.d.ts +43 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +39 -6
- package/dist/registry.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +3 -3
- package/dist/server.js.map +1 -1
- package/dist/tools.d.ts +3 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +87 -21
- package/dist/tools.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GENERATED by scripts/generate-registry.mjs — do not edit, do not commit.
|
|
3
3
|
*
|
|
4
|
-
* The mzizi registry at https://github.com/mzizi-dev/mzizi-registry/tree/
|
|
4
|
+
* The mzizi registry at https://github.com/mzizi-dev/mzizi-registry/tree/9b86e034ffd84cff9ae0214201dc4bc4b15b2885
|
|
5
5
|
* (pinned in registry.pin.json), produced by calling the registry's own API
|
|
6
6
|
* route handlers at build time. Every value is a JSON string, parsed lazily by
|
|
7
7
|
* src/registry.ts, so a cold start pays only for what a request reads.
|
|
8
8
|
*/
|
|
9
|
-
// stamp: ref=
|
|
10
|
-
export const REGISTRY_PIN = { "repository": "https://github.com/mzizi-dev/mzizi-registry", "ref": "
|
|
9
|
+
// stamp: ref=9b86e034ffd84cff9ae0214201dc4bc4b15b2885 generator=fbbfe3205035b229
|
|
10
|
+
export const REGISTRY_PIN = { "repository": "https://github.com/mzizi-dev/mzizi-registry", "ref": "9b86e034ffd84cff9ae0214201dc4bc4b15b2885" };
|
|
11
11
|
/** Whole-resource payloads, keyed by section. */
|
|
12
12
|
export const SECTIONS = {
|
|
13
13
|
"index": "{\"$schema\":\"https://ui.shadcn.com/schema/registry.json\",\"name\":\"mzizi\",\"homepage\":\"https://mzizi.dev\",\"items\":[{\"name\":\"accessibility-audit\",\"type\":\"registry:ui\",\"title\":\"Accessibility Audit\",\"description\":\"N8 assurance probe that validates WCAG compliance and color-blindness safety across every semantic-color pair. Runs daily via pg_cron, detects regressions, and files a Fundi issue for each. Uses Machado/Oliveira/Fernandes (2009) linear-RGB simulation matrices for protanopia, deuteranopia, tritanopia, and achromatopsia. Includes an audit_exempt flag so decorative borders covered by WCAG 1.4.11 non-text-contrast exemption are correctly skipped.\",\"categories\":[\"documentation\",\"assurance\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":8,\"nodeLabel\":\"assurance\",\"owner\":\"mzizi\",\"collection\":\"documentation\"},{\"name\":\"accordion\",\"type\":\"registry:ui\",\"title\":\"Accordion\",\"description\":\"A vertically stacked set of interactive headings that reveal content.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"activity-feed\",\"type\":\"registry:ui\",\"title\":\"Activity Feed\",\"description\":\"Chronological action list with timeline pattern for dashboards and profiles.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"address-input\",\"type\":\"registry:ui\",\"title\":\"Address Input\",\"description\":\"Structured address form with street, city, state, postal code, and country fields with African countries prioritized.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/input\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"admin-page\",\"type\":\"registry:ui\",\"title\":\"Admin Page\",\"description\":\"Admin/back-office page. Tabbed admin interface with sidebar navigation, content area, and action bar. For Mukoko Console admin, B2B partner dashboards, org management.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"agenda-view\",\"type\":\"registry:ui\",\"title\":\"Agenda View\",\"description\":\"Chronological event list grouped by date.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"ai-chat\",\"type\":\"registry:ui\",\"title\":\"AI Chat\",\"description\":\"Complete AI conversation interface with message history, streaming, and suggested prompts.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"ai-feedback\",\"type\":\"registry:ui\",\"title\":\"AI Feedback\",\"description\":\"Thumbs up/down rating for AI responses with optional text feedback.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"ai-response-card\",\"type\":\"registry:ui\",\"title\":\"AI Response Card\",\"description\":\"Structured AI output card with markdown rendering and action buttons.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"ai-safety\",\"type\":\"registry:lib\",\"title\":\"AI Safety\",\"description\":\"AI safety utilities: input validation, prompt injection detection, and rate limiting.\",\"categories\":[\"safety\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/observability\"],\"node\":4,\"nodeLabel\":\"safety\",\"owner\":\"mzizi\",\"collection\":\"safety\"},{\"name\":\"alert\",\"type\":\"registry:ui\",\"title\":\"Alert\",\"description\":\"Displays a callout for important information.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"alert-dialog\",\"type\":\"registry:ui\",\"title\":\"Alert Dialog\",\"description\":\"A modal dialog that interrupts the user with important content and expects a response.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"analytics-dashboard-card\",\"type\":\"registry:ui\",\"title\":\"Analytics Dashboard Card\",\"description\":\"KPI metric card with sparkline chart, period comparison (vs last week/month/year), and drill-down action. The standard analytics widget for the Nyuchi Console and all 7 Nyuchi product dashboards.\",\"categories\":[\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"analytics-dashboard-page\",\"type\":\"registry:ui\",\"title\":\"Analytics Dashboard Page\",\"description\":\"Analytics dashboard page. KPI cards, time-series charts, data tables, date range picker, export controls. Used by Mukoko Console, Nyuchi Web Services, and B2B partners. Open data compatible — sections can be made public. Competitors: Grafana, Google Analytics, Metabase.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"ancestry-memorial\",\"type\":\"registry:ui\",\"title\":\"Ancestry Memorial\",\"description\":\"Ancestral MIT display for deceased community members. Shows the person's fixed age, memorial badge, heritage data contribution status, and ancestral pool participation. Implements the visual layer of the MIT Ancestral Lifecycle Protocol.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"announcement-bar\",\"type\":\"registry:ui\",\"title\":\"Announcement Bar\",\"description\":\"Dismissible top-of-page banner for announcements with mineral-colored variants.\",\"categories\":[\"primitives\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"api-explorer-page\",\"type\":\"registry:ui\",\"title\":\"API Explorer Page\",\"description\":\"API explorer/playground page. Endpoint browser, request builder, response viewer, auth token input. For Nyuchi Web Services API docs. Competitors: Swagger UI, Postman, Stripe API Reference.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"api-key-display\",\"type\":\"registry:ui\",\"title\":\"API Key Display\",\"description\":\"Masked/revealable API key with copy and regenerate actions.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"api-usage-meter\",\"type\":\"registry:ui\",\"title\":\"API Usage Meter\",\"description\":\"API quota visualization with current usage, rate limit status, and throttling indicator. Shows requests/second, daily quota, and monthly billing period. Used in Nyuchi API Platform dashboard and developer portal.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/progress\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"app-switcher\",\"type\":\"registry:ui\",\"title\":\"App Switcher\",\"description\":\"Ecosystem app selector popover with grid of Nyuchi app icons.\",\"categories\":[\"shell\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/popover\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"appointment-card\",\"type\":\"registry:ui\",\"title\":\"Appointment Card\",\"description\":\"Medical appointment card showing provider name, specialty, date/time, location, and join/cancel actions. Maps to health.medical_appointment (Schema.org ScheduleAction). Used in Health mini-app and Nyuchi Medical provider views.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"arc-gauge\",\"type\":\"registry:ui\",\"title\":\"Arc Gauge\",\"description\":\"SVG radial arc gauge (270° sweep, open at bottom). Displays a percentage value as a filled arc with the value text centered inside. Uses stroke-dasharray animation. Proper role=\\\"meter\\\" with aria-valuenow/min/max. Used for UV index, trust scores, completion percentages, health metrics, battery levels — any bounded numeric value. Production-proven in mukoko-weather MetricCard.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/utils\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"architecture\",\"type\":\"registry:lib\",\"title\":\"Architecture\",\"description\":\"Nyuchi ecosystem architecture specification — local-first data strategy and sovereignty assessment.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"article-page\",\"type\":\"registry:ui\",\"title\":\"Article Page\",\"description\":\"Article/news reading page. Hero image, headline, author, read time, content body, source badge, related articles. Competitors: Medium, BBC, Al Jazeera.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"aspect-ratio\",\"type\":\"registry:ui\",\"title\":\"Aspect Ratio\",\"description\":\"Displays content within a desired ratio.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"assignee-picker\",\"type\":\"registry:ui\",\"title\":\"Assignee Picker\",\"description\":\"User/team member selector for task assignment. Shows avatar, name, and availability indicator. Supports single and multi-select. Used in task assignment, event organiser roles, Circle admin assignment, and any workflow where items are assigned to people.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/combobox\",\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"audio-player\",\"type\":\"registry:ui\",\"title\":\"Audio Player\",\"description\":\"Audio playback with progress bar, play/pause, and speed control.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"audio-waveform\",\"type\":\"registry:ui\",\"title\":\"Audio Waveform\",\"description\":\"Waveform visualization for audio recording, playback, and editing. Shows amplitude over time with playhead position. Used for voice notes in Campfire, audio content in Bytes, and podcast previews.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"audit-log-entry\",\"type\":\"registry:ui\",\"title\":\"Audit Log Entry\",\"description\":\"Audit event display with actor, action, target, timestamp.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"author-bio-card\",\"type\":\"registry:ui\",\"title\":\"Author Bio Card\",\"description\":\"Author profile card with avatar, bio, works count, subscriber count, revenue sharing indicator, and follow button. Maps to identity.person + novels.work tables. Used on novel detail pages and author directory.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"autocomplete\",\"type\":\"registry:ui\",\"title\":\"Autocomplete\",\"description\":\"Text input with async suggestion dropdown, loading states, and keyboard navigation.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"avatar\",\"type\":\"registry:ui\",\"title\":\"Avatar\",\"description\":\"An image element with a fallback for representing the user.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"avatar-group\",\"type\":\"registry:ui\",\"title\":\"Avatar Group\",\"description\":\"Overlapping avatar stack with +N overflow indicator for team displays.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"badge\",\"type\":\"registry:ui\",\"title\":\"Badge\",\"description\":\"Displays a badge or a component that looks like a badge.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"class-variance-authority\",\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"batch-action-bar\",\"type\":\"registry:ui\",\"title\":\"Batch Action Bar\",\"description\":\"Multi-select action toolbar that appears when items are selected in a table or list. Shows selected count with approve-all, reject-all, export, and custom action buttons. Used across all Nyuchi Console moderation and management views.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button-group\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"bento-grid\",\"type\":\"registry:ui\",\"title\":\"Bento Grid\",\"description\":\"Asymmetric grid layout with varying tile sizes. Japanese-inspired content layout pattern for dashboards, feature showcases, and landing pages. Supports responsive breakpoint-aware tile resizing. Audit flagged as missing UX pattern. ALPHA — awaiting frontend implementation.\",\"categories\":[\"primitives\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"billing-page\",\"type\":\"registry:ui\",\"title\":\"Billing Page\",\"description\":\"Billing & subscription management page. Current plan, usage meters, invoices, payment methods, upgrade CTA. Used by Nyuchi Web Services, B2B partners, and Mukoko premium subscribers.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"booking-calendar\",\"type\":\"registry:ui\",\"title\":\"Booking Calendar\",\"description\":\"Date/time availability grid for bookings. Shows available, partially available, and unavailable slots with mineral color coding. Used for vehicle booking (Transport), venue booking (Nhimbe), and appointment scheduling (Health).\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/calendar\",\"https://api.mzizi.dev/v1/ui/time-slot-picker\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"booking-confirmation\",\"type\":\"registry:ui\",\"title\":\"Booking Confirmation\",\"description\":\"Booking success card with full details, QR code, calendar-add button, and share action. The standard post-booking confirmation pattern across Nhimbe events, Transport bookings, and Health appointments. Maps to planner.reservation table.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/separator\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"booking-page\",\"type\":\"registry:ui\",\"title\":\"Booking Page\",\"description\":\"Booking/reservation page. Service/ride selection, date/time picker, provider selection, confirmation. Used by transport, health, events.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"bottom-sheet\",\"type\":\"registry:ui\",\"title\":\"Bottom Sheet\",\"description\":\"Mobile slide-up panel with drag handle, backdrop, and slide-up animation.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"breadcrumb\",\"type\":\"registry:ui\",\"title\":\"Breadcrumb\",\"description\":\"Displays the path to the current resource using a hierarchy of links.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"bulkhead\",\"type\":\"registry:lib\",\"title\":\"Bulkhead\",\"description\":\"Concurrency isolation pattern inspired by Resilience4j Bulkhead. Limits how many concurrent calls can hit a single dependency so one slow service cannot consume all available connections and starve other operations. Named after ship bulkheads that prevent a single hull breach from sinking the whole vessel. Supports both semaphore-style (max concurrent) and queue-style (max waiting) isolation. Integrates with observability for tracking concurrent call counts and rejection rates.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/observability\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"button\",\"type\":\"registry:ui\",\"title\":\"Button\",\"description\":\"Displays a button or a component that looks like a button.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\",\"class-variance-authority\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"button-group\",\"type\":\"registry:ui\",\"title\":\"Button Group\",\"description\":\"Groups related buttons together.\",\"categories\":[\"primitives\"],\"dependencies\":[\"class-variance-authority\",\"radix-ui\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/separator\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"calendar\",\"type\":\"registry:ui\",\"title\":\"Calendar\",\"description\":\"A date field component that allows users to enter and edit dates.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"react-day-picker\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"calendar-day-view\",\"type\":\"registry:ui\",\"title\":\"Calendar Day View\",\"description\":\"Single-day time grid with hourly slots and event blocks.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"calendar-month-view\",\"type\":\"registry:ui\",\"title\":\"Calendar Month View\",\"description\":\"Full month calendar grid with event dots, today indicator, and day cell click handler. Complements calendar-day-view and calendar-week-view to complete the three standard calendar views. The month grid is the most common calendar entry point across all apps.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/calendar\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"calendar-week-view\",\"type\":\"registry:ui\",\"title\":\"Calendar Week View\",\"description\":\"7-column weekly calendar view with hourly time slots.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"camera-viewfinder\",\"type\":\"registry:ui\",\"title\":\"Camera Viewfinder\",\"description\":\"Camera preview overlay with branded recording indicator, timer display, and effects panel trigger. The creation entry point for Bytes short-form video and Campfire media messages. Pill-shaped record button with malachite accent.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"canvas-chart\",\"type\":\"registry:ui\",\"title\":\"Canvas Chart\",\"description\":\"Chart.js Canvas-based chart renderer for data-heavy dashboards. Use INSTEAD of the Recharts chart when you have 100+ data points per chart or multiple charts per page. Canvas renders everything to a single DOM element regardless of data volume, while SVG (Recharts) creates a DOM node per data point. Includes CSS variable resolution for Canvas 2D context (which cannot read var() natively), theme-aware color fallbacks, mobile performance optimisation (reduced DPR, disabled animations), and automatic Chart.js instance cleanup on unmount to prevent canvas memory leaks. Production-proven in mukoko-weather with 7 charts × 365 data points.\",\"categories\":[\"primitives\"],\"dependencies\":[\"chart.js\",\"react-chartjs-2\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-tokens\",\"https://api.mzizi.dev/v1/ui/mzizi-tokens-typescript\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"caption-editor\",\"type\":\"registry:ui\",\"title\":\"Caption Editor\",\"description\":\"Overlay text editor for video captions and story text. Supports font selection (Noto Sans/Serif), text color from mineral palette, position dragging, and size adjustment. Used in Bytes and story creation.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/color-picker\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"card\",\"type\":\"registry:ui\",\"title\":\"Card\",\"description\":\"Displays a card with header, content, and footer.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"carousel\",\"type\":\"registry:ui\",\"title\":\"Carousel\",\"description\":\"A carousel with motion and swipe built using Embla.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"embla-carousel-react\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"cart-item\",\"type\":\"registry:ui\",\"title\":\"Cart Item\",\"description\":\"Shopping cart line item with quantity controls and remove action.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"category-browser\",\"type\":\"registry:ui\",\"title\":\"Category Browser\",\"description\":\"Grid of browsable categories with icons, counts, and mineral-colored accents.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"changelog-entry\",\"type\":\"registry:ui\",\"title\":\"Changelog Entry\",\"description\":\"Version update card with categorized changes.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"chaos\",\"type\":\"registry:lib\",\"title\":\"Chaos\",\"description\":\"Chaos testing utilities for injecting random errors and latency to test resilience.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/observability\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"chapter-list\",\"type\":\"registry:ui\",\"title\":\"Chapter List\",\"description\":\"Table of contents / chapter navigation for serialized works. Shows chapter titles, word counts, publish dates, and locked/unlocked status for subscription-gated content. Maps to novels.chapter table.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/accordion\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"chapter-reader\",\"type\":\"registry:ui\",\"title\":\"Chapter Reader\",\"description\":\"Optimized long-form reading view with page/scroll modes, bookmark, highlight, and note-in-margin functionality. Adjustable Noto Serif typography. The core reading experience for Novels, competing with Wattpad and Tapas reader quality.\",\"categories\":[\"primitives\"],\"dependencies\":[\"dompurify\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/scroll-area\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"chart\",\"type\":\"registry:ui\",\"title\":\"Chart\",\"description\":\"Beautiful charts built using Recharts. Copy and paste into your apps.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-area-axes\",\"type\":\"registry:block\",\"title\":\"Chart Area Axes\",\"description\":\"Area chart — axes variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-area-default\",\"type\":\"registry:block\",\"title\":\"Chart Area Default\",\"description\":\"Area chart — default variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-area-gradient\",\"type\":\"registry:block\",\"title\":\"Chart Area Gradient\",\"description\":\"Area chart — gradient variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-area-icons\",\"type\":\"registry:block\",\"title\":\"Chart Area Icons\",\"description\":\"Area chart — icons variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-area-interactive\",\"type\":\"registry:block\",\"title\":\"Chart Area Interactive\",\"description\":\"Area chart — interactive variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-area-legend\",\"type\":\"registry:block\",\"title\":\"Chart Area Legend\",\"description\":\"Area chart — legend variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-area-linear\",\"type\":\"registry:block\",\"title\":\"Chart Area Linear\",\"description\":\"Area chart — linear variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-area-stacked\",\"type\":\"registry:block\",\"title\":\"Chart Area Stacked\",\"description\":\"Area chart — stacked variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-area-stacked-expand\",\"type\":\"registry:block\",\"title\":\"Chart Area Stacked Expand\",\"description\":\"Area chart — stacked expand variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-area-step\",\"type\":\"registry:block\",\"title\":\"Chart Area Step\",\"description\":\"Area chart — step variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-bar-active\",\"type\":\"registry:block\",\"title\":\"Chart Bar Active\",\"description\":\"Bar chart — active variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-bar-default\",\"type\":\"registry:block\",\"title\":\"Chart Bar Default\",\"description\":\"Bar chart — default variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-bar-horizontal\",\"type\":\"registry:block\",\"title\":\"Chart Bar Horizontal\",\"description\":\"Bar chart — horizontal variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-bar-interactive\",\"type\":\"registry:block\",\"title\":\"Chart Bar Interactive\",\"description\":\"Bar chart — interactive variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-bar-label\",\"type\":\"registry:block\",\"title\":\"Chart Bar Label\",\"description\":\"Bar chart — label variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-bar-label-custom\",\"type\":\"registry:block\",\"title\":\"Chart Bar Label Custom\",\"description\":\"Bar chart — label custom variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-bar-mixed\",\"type\":\"registry:block\",\"title\":\"Chart Bar Mixed\",\"description\":\"Bar chart — mixed variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-bar-multiple\",\"type\":\"registry:block\",\"title\":\"Chart Bar Multiple\",\"description\":\"Bar chart — multiple variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-bar-negative\",\"type\":\"registry:block\",\"title\":\"Chart Bar Negative\",\"description\":\"Bar chart — negative variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-bar-stacked\",\"type\":\"registry:block\",\"title\":\"Chart Bar Stacked\",\"description\":\"Bar chart — stacked variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-line-default\",\"type\":\"registry:block\",\"title\":\"Chart Line Default\",\"description\":\"Line chart — default variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-line-dots\",\"type\":\"registry:block\",\"title\":\"Chart Line Dots\",\"description\":\"Line chart — dots variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-line-dots-colors\",\"type\":\"registry:block\",\"title\":\"Chart Line Dots Colors\",\"description\":\"Line chart — dots colors variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-line-dots-custom\",\"type\":\"registry:block\",\"title\":\"Chart Line Dots Custom\",\"description\":\"Line chart — dots custom variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-line-interactive\",\"type\":\"registry:block\",\"title\":\"Chart Line Interactive\",\"description\":\"Line chart — interactive variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-line-label\",\"type\":\"registry:block\",\"title\":\"Chart Line Label\",\"description\":\"Line chart — label variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-line-label-custom\",\"type\":\"registry:block\",\"title\":\"Chart Line Label Custom\",\"description\":\"Line chart — label custom variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-line-linear\",\"type\":\"registry:block\",\"title\":\"Chart Line Linear\",\"description\":\"Line chart — linear variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-line-multiple\",\"type\":\"registry:block\",\"title\":\"Chart Line Multiple\",\"description\":\"Line chart — multiple variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-line-step\",\"type\":\"registry:block\",\"title\":\"Chart Line Step\",\"description\":\"Line chart — step variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-pie-donut\",\"type\":\"registry:block\",\"title\":\"Chart Pie Donut\",\"description\":\"Pie chart — donut variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-pie-donut-active\",\"type\":\"registry:block\",\"title\":\"Chart Pie Donut Active\",\"description\":\"Pie chart — donut active variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-pie-donut-text\",\"type\":\"registry:block\",\"title\":\"Chart Pie Donut Text\",\"description\":\"Pie chart — donut text variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-pie-interactive\",\"type\":\"registry:block\",\"title\":\"Chart Pie Interactive\",\"description\":\"Pie chart — interactive variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-pie-label\",\"type\":\"registry:block\",\"title\":\"Chart Pie Label\",\"description\":\"Pie chart — label variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-pie-label-custom\",\"type\":\"registry:block\",\"title\":\"Chart Pie Label Custom\",\"description\":\"Pie chart — label custom variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-pie-label-list\",\"type\":\"registry:block\",\"title\":\"Chart Pie Label List\",\"description\":\"Pie chart — label list variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-pie-legend\",\"type\":\"registry:block\",\"title\":\"Chart Pie Legend\",\"description\":\"Pie chart — legend variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-pie-separator-none\",\"type\":\"registry:block\",\"title\":\"Chart Pie Separator None\",\"description\":\"Pie chart — separator none variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-pie-simple\",\"type\":\"registry:block\",\"title\":\"Chart Pie Simple\",\"description\":\"Pie chart — simple variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-pie-stacked\",\"type\":\"registry:block\",\"title\":\"Chart Pie Stacked\",\"description\":\"Pie chart — stacked variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radar-default\",\"type\":\"registry:block\",\"title\":\"Chart Radar Default\",\"description\":\"Radar chart — default variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radar-dots\",\"type\":\"registry:block\",\"title\":\"Chart Radar Dots\",\"description\":\"Radar chart — dots variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radar-grid-circle\",\"type\":\"registry:block\",\"title\":\"Chart Radar Grid Circle\",\"description\":\"Radar chart — grid circle variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radar-grid-circle-fill\",\"type\":\"registry:block\",\"title\":\"Chart Radar Grid Circle Fill\",\"description\":\"Radar chart — grid circle fill variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radar-grid-circle-no-lines\",\"type\":\"registry:block\",\"title\":\"Chart Radar Grid Circle No Lines\",\"description\":\"Radar chart — grid circle no lines variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radar-grid-custom\",\"type\":\"registry:block\",\"title\":\"Chart Radar Grid Custom\",\"description\":\"Radar chart — grid custom variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radar-grid-fill\",\"type\":\"registry:block\",\"title\":\"Chart Radar Grid Fill\",\"description\":\"Radar chart — grid fill variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radar-grid-none\",\"type\":\"registry:block\",\"title\":\"Chart Radar Grid None\",\"description\":\"Radar chart — grid none variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radar-icons\",\"type\":\"registry:block\",\"title\":\"Chart Radar Icons\",\"description\":\"Radar chart — icons variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radar-label-custom\",\"type\":\"registry:block\",\"title\":\"Chart Radar Label Custom\",\"description\":\"Radar chart — label custom variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radar-legend\",\"type\":\"registry:block\",\"title\":\"Chart Radar Legend\",\"description\":\"Radar chart — legend variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radar-lines-only\",\"type\":\"registry:block\",\"title\":\"Chart Radar Lines Only\",\"description\":\"Radar chart — lines only variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radar-multiple\",\"type\":\"registry:block\",\"title\":\"Chart Radar Multiple\",\"description\":\"Radar chart — multiple variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radar-radius\",\"type\":\"registry:block\",\"title\":\"Chart Radar Radius\",\"description\":\"Radar chart — radius variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radial-grid\",\"type\":\"registry:block\",\"title\":\"Chart Radial Grid\",\"description\":\"Radial chart — grid variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radial-label\",\"type\":\"registry:block\",\"title\":\"Chart Radial Label\",\"description\":\"Radial chart — label variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radial-shape\",\"type\":\"registry:block\",\"title\":\"Chart Radial Shape\",\"description\":\"Radial chart — shape variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radial-simple\",\"type\":\"registry:block\",\"title\":\"Chart Radial Simple\",\"description\":\"Radial chart — simple variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radial-stacked\",\"type\":\"registry:block\",\"title\":\"Chart Radial Stacked\",\"description\":\"Radial chart — stacked variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-radial-text\",\"type\":\"registry:block\",\"title\":\"Chart Radial Text\",\"description\":\"Radial chart — text variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-tooltip-advanced\",\"type\":\"registry:block\",\"title\":\"Chart Tooltip Advanced\",\"description\":\"Tooltip chart — advanced variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-tooltip-default\",\"type\":\"registry:block\",\"title\":\"Chart Tooltip Default\",\"description\":\"Tooltip chart — default variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-tooltip-formatter\",\"type\":\"registry:block\",\"title\":\"Chart Tooltip Formatter\",\"description\":\"Tooltip chart — formatter variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-tooltip-icons\",\"type\":\"registry:block\",\"title\":\"Chart Tooltip Icons\",\"description\":\"Tooltip chart — icons variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-tooltip-indicator-line\",\"type\":\"registry:block\",\"title\":\"Chart Tooltip Indicator Line\",\"description\":\"Tooltip chart — indicator line variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-tooltip-indicator-none\",\"type\":\"registry:block\",\"title\":\"Chart Tooltip Indicator None\",\"description\":\"Tooltip chart — indicator none variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-tooltip-label-custom\",\"type\":\"registry:block\",\"title\":\"Chart Tooltip Label Custom\",\"description\":\"Tooltip chart — label custom variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-tooltip-label-formatter\",\"type\":\"registry:block\",\"title\":\"Chart Tooltip Label Formatter\",\"description\":\"Tooltip chart — label formatter variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chart-tooltip-label-none\",\"type\":\"registry:block\",\"title\":\"Chart Tooltip Label None\",\"description\":\"Tooltip chart — label none variant.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"chat-bubble\",\"type\":\"registry:ui\",\"title\":\"Chat Bubble\",\"description\":\"Single message display with sent/received variants, avatar, timestamp, and status.\",\"categories\":[\"primitives\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"chat-input\",\"type\":\"registry:ui\",\"title\":\"Chat Input\",\"description\":\"Message composer with auto-growing textarea, send button, and keyboard shortcuts.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"chat-layout\",\"type\":\"registry:ui\",\"title\":\"Chat Layout\",\"description\":\"Full chat page layout with sidebar conversations and main chat area.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"chat-list\",\"type\":\"registry:ui\",\"title\":\"Chat List\",\"description\":\"Scrollable conversation list with unread counts and active highlight.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"chat-page\",\"type\":\"registry:ui\",\"title\":\"Chat Page\",\"description\":\"Chat/messaging page composition. Conversation list or active chat with message input, media attachments, read receipts. Competitors: WhatsApp, Telegram, WeChat.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"checkbox\",\"type\":\"registry:ui\",\"title\":\"Checkbox\",\"description\":\"A control that allows the user to toggle between checked and not checked.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"checklist\",\"type\":\"registry:ui\",\"title\":\"Checklist\",\"description\":\"Interactive checklist with add, reorder, and completion tracking.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"checkout-page\",\"type\":\"registry:ui\",\"title\":\"Checkout Page\",\"description\":\"Checkout/order page. Cart summary, shipping, payment method selection, order confirmation. Multi-step with progress indicator.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"circuit-breaker\",\"type\":\"registry:lib\",\"title\":\"Circuit Breaker\",\"description\":\"Netflix Hystrix-inspired circuit breaker with CLOSED/OPEN/HALF_OPEN state machine.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/timeout\",\"https://api.mzizi.dev/v1/ui/observability\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"co-author-strip\",\"type\":\"registry:ui\",\"title\":\"Co Author Strip\",\"description\":\"Multi-author display showing all co-authors with their revenue split percentages. Compact inline strip for novel headers and detail pages. Maps to novels.work_contributor with revenue_share_pct.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar-group\",\"https://api.mzizi.dev/v1/ui/tooltip\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"code-block\",\"type\":\"registry:ui\",\"title\":\"Code Block\",\"description\":\"Enhanced code display with language badge, line numbers, copy button, and filename.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"code-editor\",\"type\":\"registry:ui\",\"title\":\"Code Editor\",\"description\":\"Code input with line numbers, monospace font, and tab key support.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"code-tabs\",\"type\":\"registry:ui\",\"title\":\"Code Tabs\",\"description\":\"Tabbed code display for multiple languages/frameworks.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"collapsible\",\"type\":\"registry:ui\",\"title\":\"Collapsible\",\"description\":\"An interactive component which expands and collapses a panel.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"color-picker\",\"type\":\"registry:ui\",\"title\":\"Color Picker\",\"description\":\"Color selector with hex input and preset swatches including the Seven African Minerals.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/input\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"combobox\",\"type\":\"registry:ui\",\"title\":\"Combobox\",\"description\":\"Autocomplete input and command palette with a list of suggestions.\",\"categories\":[\"primitives\"],\"dependencies\":[\"@base-ui/react\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/input-group\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"command\",\"type\":\"registry:ui\",\"title\":\"Command\",\"description\":\"Fast, composable, unstyled command menu for React.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"cmdk\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/dialog\",\"https://api.mzizi.dev/v1/ui/input-group\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"command-center\",\"type\":\"registry:block\",\"title\":\"Command Center\",\"description\":\"Spotlight-style command palette with search, grouped results, and keyboard hints.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/command\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"comment-on-paragraph\",\"type\":\"registry:ui\",\"title\":\"Comment On Paragraph\",\"description\":\"Inline paragraph-level commenting — the Medium-style annotation pattern. Users highlight text in a chapter and leave comments anchored to that specific passage. Maps to a future comments schema on novels.chapter.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/popover\",\"https://api.mzizi.dev/v1/ui/textarea\",\"https://api.mzizi.dev/v1/ui/avatar\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"comment-thread\",\"type\":\"registry:ui\",\"title\":\"Comment Thread\",\"description\":\"Threaded comments with nested replies and actions.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"console-dashboard-page\",\"type\":\"registry:ui\",\"title\":\"Console Dashboard Page\",\"description\":\"Nyuchi Console main dashboard. Project overview, resource usage, API traffic, deploy status, quick actions. The control center for Nyuchi Web Services. Competitors: Vercel Dashboard, AWS Console, Cloudflare Dashboard.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"contact-card\",\"type\":\"registry:ui\",\"title\":\"Contact Card\",\"description\":\"Contact information card with avatar, details, and action links for call and email.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"content-calendar\",\"type\":\"registry:ui\",\"title\":\"Content Calendar\",\"description\":\"Editorial calendar showing scheduled content across a week or month view. Drag-and-drop scheduling, content type color coding, and publish status indicators. Used in Mukoko News editorial management and Bytes creator scheduling.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/calendar-week-view\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"context-menu\",\"type\":\"registry:ui\",\"title\":\"Context Menu\",\"description\":\"Displays a menu to the user triggered by a right click.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"contrast-mode-toggle\",\"type\":\"registry:ui\",\"title\":\"Contrast Mode Toggle\",\"description\":\"Accessibility mode switcher supporting light, dark, high-contrast, and system-preference modes. Goes beyond simple dark mode to include APCA 3.0 AAA high-contrast for users with visual impairments.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/toggle-group\",\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"cookie-consent\",\"type\":\"registry:ui\",\"title\":\"Cookie Consent\",\"description\":\"GDPR/POPIA-compliant consent banner with accept, decline, and manage options.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"copy-button\",\"type\":\"registry:ui\",\"title\":\"Copy Button\",\"description\":\"A button that copies text to the clipboard with visual feedback on success.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"currency-input\",\"type\":\"registry:ui\",\"title\":\"Currency Input\",\"description\":\"Multi-currency numeric input with currency switcher supporting MXT, ZWL, USD, ZAR, KES, and mobile money codes. Formats values per locale with proper decimal handling. The standard financial input for all payment flows across the ecosystem.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/select\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"dao-proposal-card\",\"type\":\"registry:ui\",\"title\":\"Dao Proposal Card\",\"description\":\"Governance proposal card with title, description, proposer, voting progress bar (for/against/abstain), deadline countdown, quorum status, and vote action. Maps to ubuntu.dao_proposals table. Used in the governance section of the Mukoko Foundation interface.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/progress\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/avatar\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"dashboard-01\",\"type\":\"registry:block\",\"title\":\"Dashboard 01\",\"description\":\"Full dashboard layout with stats, chart section, and activity feed.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"data-explorer-page\",\"type\":\"registry:ui\",\"title\":\"Data Explorer Page\",\"description\":\"Data explorer page for open data. Table browser, SQL/filter builder, column selection, pagination, CSV/JSON export. Used by Nyuchi Open Data platform and B2B partners. Competitors: Metabase, data.gov, Socrata.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"data-table\",\"type\":\"registry:ui\",\"title\":\"Data Table\",\"description\":\"A powerful table with sorting, filtering, pagination, and column visibility powered by TanStack Table.\",\"categories\":[\"primitives\"],\"dependencies\":[\"@tanstack/react-table\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/dropdown-menu\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/table\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"date-label\",\"type\":\"registry:ui\",\"title\":\"Date Label\",\"description\":\"Smart date display that shows relative time for recent dates (\\\"2 hours ago\\\", \\\"yesterday\\\") and absolute dates for older ones (\\\"15 May 2026\\\"). Respects locale from mzizi-locale. A micro-primitive used everywhere — message timestamps, card metadata, activity feeds, task due dates.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/tooltip\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"date-picker\",\"type\":\"registry:ui\",\"title\":\"Date Picker\",\"description\":\"A date picker component with calendar popover, supporting single date and date range selection.\",\"categories\":[\"primitives\"],\"dependencies\":[\"date-fns\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/calendar\",\"https://api.mzizi.dev/v1/ui/popover\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"date-range-picker\",\"type\":\"registry:ui\",\"title\":\"Date Range Picker\",\"description\":\"Dual-calendar popover for selecting date ranges with preset quick-selects.\",\"categories\":[\"primitives\"],\"dependencies\":[\"react-day-picker\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"dependency-graph\",\"type\":\"registry:ui\",\"title\":\"Dependency Graph\",\"description\":\"Visual dependency/relationship display between items — task depends on task, event requires event, etc. Shows directed edges between nodes. Used in project planning (Planner), workflow visualization (Console), and content relationship mapping.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"deployment-status-card\",\"type\":\"registry:ui\",\"title\":\"Deployment Status Card\",\"description\":\"Service health card showing uptime percentage, error rate, response time (p50/p95/p99), and last deploy timestamp. Mineral color coding: malachite=healthy, gold=degraded, terracotta=down. Used in the Nyuchi Console infrastructure dashboard.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/status-indicator\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"description-list\",\"type\":\"registry:ui\",\"title\":\"Description List\",\"description\":\"Semantic key-value display using dl/dt/dd elements.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"dialog\",\"type\":\"registry:ui\",\"title\":\"Dialog\",\"description\":\"A window overlaid on the primary window, rendering content underneath inert.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"direction\",\"type\":\"registry:ui\",\"title\":\"Direction\",\"description\":\"A provider to set the text direction for the application.\",\"categories\":[\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"drag-handle\",\"type\":\"registry:ui\",\"title\":\"Drag Handle\",\"description\":\"Grip indicator for drag-and-drop reordering.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"drawer\",\"type\":\"registry:ui\",\"title\":\"Drawer\",\"description\":\"A drawer component for React built on top of Vaul.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"vaul\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"driver-profile-card\",\"type\":\"registry:ui\",\"title\":\"Driver Profile Card\",\"description\":\"Driver/operator card with photo, rating, vehicle info, verification badge, and trip count. Maps to identity.person + transport tables via Nyuchi Logistics. Used in Transport booking confirmation and ride-matching.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/rating\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"dropdown-menu\",\"type\":\"registry:ui\",\"title\":\"Dropdown Menu\",\"description\":\"Displays a menu to the user triggered by a button.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"empty\",\"type\":\"registry:ui\",\"title\":\"Empty\",\"description\":\"Displays an empty state with an icon, title, and description.\",\"categories\":[\"primitives\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"empty-state\",\"type\":\"registry:block\",\"title\":\"Empty State\",\"description\":\"Raw empty state primitive with icon, title, description, and call-to-action slots. No brand opinion. Use mzizi-empty-state (Layer 3) for the branded ecosystem version with mineral accents.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"endpoint-card\",\"type\":\"registry:ui\",\"title\":\"Endpoint Card\",\"description\":\"API endpoint display with method badge, path, and description.\",\"categories\":[\"primitives\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"env-editor\",\"type\":\"registry:ui\",\"title\":\"Env Editor\",\"description\":\"Key-value environment variable editor with secret masking.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"error-boundary\",\"type\":\"registry:ui\",\"title\":\"Error Boundary\",\"description\":\"A React error boundary component that catches render errors and displays a fallback.\",\"categories\":[\"resilience\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/observability\"],\"node\":5,\"nodeLabel\":\"resilience\",\"owner\":\"mzizi\",\"collection\":\"resilience\"},{\"name\":\"error-page\",\"type\":\"registry:block\",\"title\":\"Error Page\",\"description\":\"Standard error page for 404, 500, and 503 states with mineral accent.\",\"categories\":[\"resilience\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\"],\"node\":5,\"nodeLabel\":\"resilience\",\"owner\":\"mzizi\",\"collection\":\"resilience\"},{\"name\":\"escrow-status\",\"type\":\"registry:ui\",\"title\":\"Escrow Status\",\"description\":\"BushTrade escrow payment status tracker. Shows payment lifecycle stages: initiated → held → released/disputed. Maps to wallet.payment_intents with escrow state. Used in BushTrade transaction views and seller dashboards.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/stepper\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"event-block\",\"type\":\"registry:ui\",\"title\":\"Event Block\",\"description\":\"Time-ranged event block for rendering inside calendar day/week grids. Shows title, time span, and color category. Supports overlapping events with column stacking. The visual unit that populates calendar-day-view and calendar-week-view slots.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"event-rsvp-inline\",\"type\":\"registry:ui\",\"title\":\"Event Rsvp Inline\",\"description\":\"In-chat event RSVP card shared in Campfire conversations. Compact event preview with date, venue, and quick RSVP buttons (Going/Maybe/Decline). Maps to events.rsvp_action. Bridges Nhimbe events into the Campfire messaging flow.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/button-group\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"exchange-rate\",\"type\":\"registry:ui\",\"title\":\"Exchange Rate\",\"description\":\"Currency pair display with live rate, conversion calculator, and trend indicator. Supports MXT ↔ fiat pairs across 42 African currencies. Used in Wallet swap interface and BushTrade cross-border commerce.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/currency-input\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"fallback-chain\",\"type\":\"registry:lib\",\"title\":\"Fallback Chain\",\"description\":\"Sequential fallback strategy — try stages in order, return first success.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/timeout\",\"https://api.mzizi.dev/v1/ui/observability\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"fare-calculator\",\"type\":\"registry:ui\",\"title\":\"Fare Calculator\",\"description\":\"Route fare display with breakdown by segment, applicable discounts, and total. Supports multi-currency display. Used in Transport for route cost estimation before booking.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/separator\",\"https://api.mzizi.dev/v1/ui/currency-input\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"feature-flag-toggle\",\"type\":\"registry:ui\",\"title\":\"Feature Flag Toggle\",\"description\":\"Feature flag row with name, description, environment toggles (dev/staging/production), and rollout percentage slider. Used in the Nyuchi Console feature management dashboard.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/switch\",\"https://api.mzizi.dev/v1/ui/slider\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"field\",\"type\":\"registry:ui\",\"title\":\"Field\",\"description\":\"A form field component with label, description, and message.\",\"categories\":[\"primitives\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/label\",\"https://api.mzizi.dev/v1/ui/separator\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"file-preview\",\"type\":\"registry:ui\",\"title\":\"File Preview\",\"description\":\"File preview card with icon, name, and size display.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"file-upload\",\"type\":\"registry:ui\",\"title\":\"File Upload\",\"description\":\"A drag-and-drop file upload zone with file list, size validation, and remove actions.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"filter-bar\",\"type\":\"registry:ui\",\"title\":\"Filter Bar\",\"description\":\"A horizontal bar of toggleable filter chips with optional counts and a clear button.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"font-settings-panel\",\"type\":\"registry:ui\",\"title\":\"Font Settings Panel\",\"description\":\"Reader customization panel for font size, line height, theme (light/dark/sepia), font family (Noto Sans/Serif/system), and text alignment. Persisted per-user in the Digital Twin preferences. Used in Novels reader and News article reader mode.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/slider\",\"https://api.mzizi.dev/v1/ui/radio-group\",\"https://api.mzizi.dev/v1/ui/card\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"form\",\"type\":\"registry:ui\",\"title\":\"Form\",\"description\":\"Building forms with React Hook Form and Zod.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"@radix-ui/react-label\",\"@radix-ui/react-slot\",\"react-hook-form\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"gas-estimator\",\"type\":\"registry:ui\",\"title\":\"Gas Estimator\",\"description\":\"Transaction cost preview showing estimated gas fee in NHC with fiat equivalent. Highlights that MIT holders have static gas prices per Honeycomb protocol. Used before any on-chain transaction in Wallet.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"group-info-card\",\"type\":\"registry:ui\",\"title\":\"Group Info Card\",\"description\":\"Circle/group info header with cover image, name, description, member count, privacy status, rules summary, and admin list. Used at the top of Circle detail pages and group settings. Maps to circles.circle table.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/avatar-group\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"health-dashboard-page\",\"type\":\"registry:ui\",\"title\":\"Health Dashboard Page\",\"description\":\"Health dashboard page. Vitals overview, activity rings, medication reminders, upcoming appointments, health score. Competitors: Apple Health, MyFitnessPal, Babylon.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"health-record-summary\",\"type\":\"registry:ui\",\"title\":\"Health Record Summary\",\"description\":\"Encrypted health record summary card showing recent visits, active prescriptions, allergies, and conditions. Data sourced from the sovereign Digital Twin pod — NEVER from Supabase. Respects Covenant One.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/accordion\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"horizontal-scroll\",\"type\":\"registry:ui\",\"title\":\"Horizontal Scroll\",\"description\":\"Horizontal scroll container with CSS scroll-snap for mobile-friendly card browsing. Hides scrollbar, supports snap-to-item alignment. Used for hourly forecast cards, category browsers, story rings, product carousels, and any horizontal list that should swipe naturally on mobile. Proper role=\\\"list\\\" with keyboard navigation.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/utils\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"hover-card\",\"type\":\"registry:ui\",\"title\":\"Hover Card\",\"description\":\"For sighted users to preview content available behind a link.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"individual-profile-page\",\"type\":\"registry:ui\",\"title\":\"Individual Profile Page\",\"description\":\"Individual user profile page. Avatar, bio, verification tier, trust score, activity stats, content tabs (posts, media, listings, reviews). Competitors: LinkedIn, Instagram, WeChat.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"infinite-scroll\",\"type\":\"registry:ui\",\"title\":\"Infinite Scroll\",\"description\":\"IntersectionObserver-based scroll sentinel for loading more content.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"info-row\",\"type\":\"registry:ui\",\"title\":\"Info Row\",\"description\":\"Simple label + value display row with optional icon. Used on detail pages for key-value information: \\\"Temperature: 28°C\\\", \\\"Location: Harare\\\", \\\"Status: Active\\\". Compact single-line layout with icon left, label middle, value right-aligned. Production-proven in mukoko-weather for atmospheric details.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/utils\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"infrastructure-page\",\"type\":\"registry:ui\",\"title\":\"Infrastructure Page\",\"description\":\"Infrastructure monitoring page. Service health grid, deployment history, resource utilization gauges, incident timeline. For Nyuchi Web Services ops. Competitors: Datadog, New Relic.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"inline-edit\",\"type\":\"registry:ui\",\"title\":\"Inline Edit\",\"description\":\"Click-to-edit text field that transforms from display mode to input mode on interaction. Pattern for low-friction editing in admin tables, settings pages, and profile fields. Supports validation, optimistic save, and revert on escape. Audit flagged as missing UX pattern. ALPHA — awaiting frontend implementation.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"input\",\"type\":\"registry:ui\",\"title\":\"Input\",\"description\":\"Displays a form input field.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"input-group\",\"type\":\"registry:ui\",\"title\":\"Input Group\",\"description\":\"Groups input, button, and addon elements together.\",\"categories\":[\"primitives\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/textarea\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"input-otp\",\"type\":\"registry:ui\",\"title\":\"Input OTP\",\"description\":\"Accessible one-time password component with copy paste functionality.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"input-otp\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"invite-link\",\"type\":\"registry:ui\",\"title\":\"Invite Link\",\"description\":\"Shareable invite card with QR code, copyable link, expiry countdown, and usage count. Used for Circle invitations, event invites (Nhimbe), and referral links. Mineral-colored QR code.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/copy-button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"invoice-row\",\"type\":\"registry:ui\",\"title\":\"Invoice Row\",\"description\":\"Invoice line item with date, amount, status badge, and download.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"item\",\"type\":\"registry:ui\",\"title\":\"Item\",\"description\":\"A flexible item component for lists, menus, and settings.\",\"categories\":[\"primitives\"],\"dependencies\":[\"class-variance-authority\",\"radix-ui\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/separator\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"itinerary-timeline\",\"type\":\"registry:ui\",\"title\":\"Itinerary Timeline\",\"description\":\"Multi-leg journey planner view showing sequential stops with transport modes, transfer points, and timing. Used in Transport for commute planning and in Planner for multi-event day views.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/timeline\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/separator\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"job-board-page\",\"type\":\"registry:ui\",\"title\":\"Job Board Page\",\"description\":\"Job board/listings page. Search, filters (location, type, salary, industry), list of job cards. Competitors: LinkedIn, Indeed, Glassdoor.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"json-viewer\",\"type\":\"registry:ui\",\"title\":\"Json Viewer\",\"description\":\"Collapsible JSON tree view with syntax coloring and type indicators.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"kanban-board\",\"type\":\"registry:ui\",\"title\":\"Kanban Board\",\"description\":\"Column-based board for organizing items by status with drag indicators.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"kbd\",\"type\":\"registry:ui\",\"title\":\"Kbd\",\"description\":\"Displays a keyboard shortcut or key combination.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"kpi-card\",\"type\":\"registry:ui\",\"title\":\"Kpi Card\",\"description\":\"Key Performance Indicator card. Displays a metric label, large value, optional change percentage with trend direction. Extracted from analytics-dashboard-page, console-dashboard-page, and wallet-page where the same pattern was repeated inline. Brand-agnostic — uses semantic CSS vars only.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"label\",\"type\":\"registry:ui\",\"title\":\"Label\",\"description\":\"Renders an accessible label associated with controls.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"language-switcher\",\"type\":\"registry:ui\",\"title\":\"Language Switcher\",\"description\":\"In-app language selector with locale display. Prioritizes the 16 Zimbabwean official languages (Shona, Ndebele first), then African languages, then global. Shows current language with flag/code indicator. Critical for a platform serving 54 countries and 2000+ languages.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/select\",\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"lazy-section\",\"type\":\"registry:ui\",\"title\":\"Lazy Section\",\"description\":\"TikTok-style progressive lazy section with sequential mount queue and bidirectional visibility. Only ONE section mounts at a time via a global FIFO queue, preventing DOM/Canvas surges that OOM mobile browsers. Sections unmount when scrolled far off-screen (1500px margin) to cap peak memory. Adaptive timing: 150ms settle on mobile, 50ms on desktop. Production-proven in mukoko-weather with 7 Chart.js sections. The registry's most critical performance primitive.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/use-mobile\",\"https://api.mzizi.dev/v1/ui/observability\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"learning-page\",\"type\":\"registry:ui\",\"title\":\"Learning Page\",\"description\":\"Learning/education page. Course content, lesson progress, quiz area, notes. Competitors: Duolingo, Coursera, Khan Academy.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"lightbox\",\"type\":\"registry:ui\",\"title\":\"Lightbox\",\"description\":\"Full-screen image viewer with gallery navigation.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"location-picker\",\"type\":\"registry:ui\",\"title\":\"Location Picker\",\"description\":\"Interactive map-based location selection form control. User taps/clicks map to set coordinates, with search input for address lookup. Returns lat/lng and formatted address. Used in event creation (Nhimbe), business listing (BushTrade/Places), and any form that needs a geographic input.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/map-view\",\"https://api.mzizi.dev/v1/ui/input\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"location-share\",\"type\":\"registry:ui\",\"title\":\"Location Share\",\"description\":\"Live/static location sharing card in chat. Shows a mini map preview with pin, address, and share duration (for live). Used in Campfire for meeting coordination, Transport for pickup points, and Places for recommendations.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/map-view\",\"https://api.mzizi.dev/v1/ui/card\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"log-viewer\",\"type\":\"registry:ui\",\"title\":\"Log Viewer\",\"description\":\"Scrollable filterable log output with severity coloring and auto-scroll.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"login-01\",\"type\":\"registry:block\",\"title\":\"Login 01\",\"description\":\"Centered card login with email/password and mineral accent stripe.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"login-02\",\"type\":\"registry:block\",\"title\":\"Login 02\",\"description\":\"Split layout login with brand image left, form right.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"login-03\",\"type\":\"registry:block\",\"title\":\"Login 03\",\"description\":\"Social login buttons (Google, GitHub) plus email/password form.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"login-04\",\"type\":\"registry:block\",\"title\":\"Login 04\",\"description\":\"OTP/magic link style login with code input.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"login-05\",\"type\":\"registry:block\",\"title\":\"Login 05\",\"description\":\"Two-column login with brand showcase and mineral colors.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"logs-page\",\"type\":\"registry:ui\",\"title\":\"Logs Page\",\"description\":\"Logs viewer page. Real-time log stream, severity filters, search, time range, structured log expansion. For Nyuchi Web Services debugging and compliance audit trails.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"maintenance-page\",\"type\":\"registry:ui\",\"title\":\"Maintenance Page\",\"description\":\"Full-page maintenance mode display with estimated return time.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"map-card\",\"type\":\"registry:ui\",\"title\":\"Map Card\",\"description\":\"Floating preview card that appears on map marker interaction. Shows a compact listing-card inside the map context with image, title, category, and action. Bridges the map view to the listing detail flow.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/map-view\",\"https://api.mzizi.dev/v1/ui/card\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"map-cluster\",\"type\":\"registry:ui\",\"title\":\"Map Cluster\",\"description\":\"Grouped marker display for high-density map areas. Shows count badge with mineral accent and expands on interaction. Used when multiple listings/places overlap at the current zoom level.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/map-view\",\"https://api.mzizi.dev/v1/ui/map-marker\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"map-marker\",\"type\":\"registry:ui\",\"title\":\"Map Marker\",\"description\":\"Custom branded map pin with mineral-colored variants and verification tier indicators. Supports default, verified, featured, and cluster states. The pin shape uses the Nyuchi pill radius for consistency. Maps to places.places and events.event tables.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/map-view\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"map-page\",\"type\":\"registry:ui\",\"title\":\"Map Page\",\"description\":\"Map/navigation page. Full-screen map with search, pin details, route overlay, bottom sheet for location info. Competitors: Google Maps, Uber, Bolt.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"map-route\",\"type\":\"registry:ui\",\"title\":\"Map Route\",\"description\":\"Route visualization overlay for map with waypoints, distance, and estimated time. Supports driving, walking, transit, and cycling modes. Used by Transport mini-app for commute planning and route display.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/map-view\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"map-view\",\"type\":\"registry:ui\",\"title\":\"Map View\",\"description\":\"Embeddable map container with design system tokens, mineral-colored controls, and branded attribution. Wraps Mapbox GL JS with Nyuchi theme integration. Supports tile layers, zoom controls, geolocation, and fullscreen toggle. The foundational map primitive for Places, Transport, BushTrade, Nhimbe, and any app that shows a geographic context.\",\"categories\":[\"primitives\"],\"dependencies\":[\"mapbox-gl\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"markdown-renderer\",\"type\":\"registry:ui\",\"title\":\"Markdown Renderer\",\"description\":\"Themed markdown-to-HTML renderer with design system typography and colors.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"marketplace-page\",\"type\":\"registry:ui\",\"title\":\"Marketplace Page\",\"description\":\"Marketplace/storefront browsing page. Search, category filters, grid/list toggle, product cards. Competitors: Jumia, Amazon, Alibaba, Facebook Marketplace.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"masonry-grid\",\"type\":\"registry:ui\",\"title\":\"Masonry Grid\",\"description\":\"CSS columns-based variable-height grid layout.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"media-filter-strip\",\"type\":\"registry:ui\",\"title\":\"Media Filter Strip\",\"description\":\"Horizontal scrollable filter/effect picker — the TikTok/Instagram paradigm. Shows filter previews as circular thumbnails with names. Applies visual effects to camera preview or captured media. Used in Bytes creation flow.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/scroll-area\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"media-player-page\",\"type\":\"registry:ui\",\"title\":\"Media Player Page\",\"description\":\"Full-screen media player page. Video/audio player with controls, comments, related content. Competitors: YouTube, TikTok, Netflix, Spotify.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"medication-reminder\",\"type\":\"registry:ui\",\"title\":\"Medication Reminder\",\"description\":\"Medication schedule card with drug name, dosage, timing intervals, adherence tracking, and snooze/take actions. Maps to health.medication_reminder table. Stores reminders in the sovereign Digital Twin pod.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/progress\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"mega-menu\",\"type\":\"registry:ui\",\"title\":\"Mega Menu\",\"description\":\"Full-width flyout navigation with columns of links grouped by section.\",\"categories\":[\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/popover\",\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"member-list\",\"type\":\"registry:ui\",\"title\":\"Member List\",\"description\":\"Scrollable member directory with role badges (admin/moderator/member), online status dots, join date, and search filtering. Used in Circle member management and event attendee lists. Maps to circles.circle_membership table.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/search-bar\",\"https://api.mzizi.dev/v1/ui/virtual-list\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"member-row\",\"type\":\"registry:ui\",\"title\":\"Member Row\",\"description\":\"User/member display row with avatar, name, email/subtitle, role badge, and status indicator. Extracted from team-management-page and chat-page. Brand-agnostic primitive.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"mention-input\",\"type\":\"registry:ui\",\"title\":\"Mention Input\",\"description\":\"Text input with @mention autocomplete for users.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"menubar\",\"type\":\"registry:ui\",\"title\":\"Menubar\",\"description\":\"A visually persistent menu common in desktop applications.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"message-thread\",\"type\":\"registry:ui\",\"title\":\"Message Thread\",\"description\":\"Nested reply thread with indentation and collapse/expand.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"mfa-setup\",\"type\":\"registry:ui\",\"title\":\"Mfa Setup\",\"description\":\"MFA enrollment card with QR code and verification step.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"mobile-money-selector\",\"type\":\"registry:ui\",\"title\":\"Mobile Money Selector\",\"description\":\"Payment method picker for African mobile money services — EcoCash, MTN MoMo, Airtel Money, M-Pesa, and Innbucks. Shows provider logo, phone number input, and USSD code reference. This is HOW Africa pays — the primary payment primitive for the continent.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/radio-group\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"moderation-queue-item\",\"type\":\"registry:ui\",\"title\":\"Moderation Queue Item\",\"description\":\"Content review item showing flagged content preview, report reason, reporter info, and approve/reject/escalate action buttons. Maps to system.content_moderation. Used in the Nyuchi Console moderation dashboard.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/button-group\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/avatar\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"music-selector\",\"type\":\"registry:ui\",\"title\":\"Music Selector\",\"description\":\"Audio track browser and selector for video creation. Search, preview, and attach music tracks to Bytes content. Shows track name, artist, duration, and waveform preview. Used in Bytes post-capture editing.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/search-bar\",\"https://api.mzizi.dev/v1/ui/audio-waveform\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"mzizi-a11y\",\"type\":\"registry:lib\",\"title\":\"mzizi a11y\",\"description\":\"Accessibility infrastructure for the Nyuchi ecosystem. Exports FocusTrap (modal/overlay focus containment), LiveRegion (screen reader announcements for dynamic content), SkipNav (keyboard skip-to-content link), VisuallyHidden (screen-reader-only text), and a11y token definitions (focus ring styles, minimum touch targets, contrast-safe mineral pairings). Also exports useAnnounce hook for imperatively announcing trust score changes, notification arrivals, loading completions, and other dynamic updates to screen readers. Target: Rust compiled to WebAssembly. wasm-bindgen exposes the interface to SvelteKit. One Rust codebase compiles to both WASM (browser) and native binary (server).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-tokens\"],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-a11y-audit\",\"type\":\"registry:lib\",\"title\":\"mzizi a11y Audit\",\"description\":\"Runtime accessibility compliance checker. Scans rendered DOM for WCAG violations: missing ARIA labels, insufficient contrast, missing alt text, broken focus order, touch target sizes. Can run on-demand or continuously in development. Competitors: Lighthouse, axe-core.\",\"categories\":[\"observability\",\"assurance\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":8,\"nodeLabel\":\"assurance\",\"owner\":\"mzizi\",\"collection\":\"observability\"},{\"name\":\"mzizi-abr-content\",\"type\":\"registry:ui\",\"title\":\"mzizi Abr Content\",\"description\":\"Adaptive Bitrate Content — adjusts content quality based on network conditions. Serves lower-resolution images, compressed text, and deferred media on slow connections. Based on TikTok ABR streaming but applied to all content types, not just video. Critical for Africa where 2G/3G is common.\",\"categories\":[\"resilience\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":5,\"nodeLabel\":\"resilience\",\"owner\":\"mzizi\",\"collection\":\"resilience\"},{\"name\":\"mzizi-action-sheet\",\"type\":\"registry:ui\",\"title\":\"mzizi Action Sheet\",\"description\":\"Universal branded bottom action sheet for contextual actions on any content across the ecosystem. Share, save, report, copy link, block, follow — the standard set of actions available on listings, posts, profiles, and messages. Renders as a bottom sheet on mobile with mineral accent on destructive/primary actions. Composes sheet and share-dialog primitives.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/sheet\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-ai-context\",\"type\":\"registry:lib\",\"title\":\"mzizi AI Context\",\"description\":\"AI context provider for N10. Generates context windows for AI assistants (Claude, Cursor, Copilot, Windsurf) with current ecosystem model, node architecture, and coding rules. No hardcoded counts — always accepts live values from get_system_counts(). Used by the MCP server and IDE integrations.\",\"categories\":[\"documentation-engine\",\"documentation\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":10,\"nodeLabel\":\"documentation\",\"owner\":\"nyuchi\",\"collection\":\"documentation-engine\"},{\"name\":\"mzizi-alert-banner\",\"type\":\"registry:ui\",\"title\":\"mzizi Alert Banner\",\"description\":\"Universal branded alert banner with mineral-coded severity levels. Used across all ecosystem apps for weather alerts, system notifications, security warnings, trust advisories, and any time-sensitive information. Severity maps to minerals: watch=cobalt, moderate=terracotta, severe=gold. Renders as a prominent banner with dismiss, detail action, and safety instructions.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/alert\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-alert-engine\",\"type\":\"registry:lib\",\"title\":\"mzizi Alert Engine\",\"description\":\"SLO/SLA tracking and threshold-based alerting with escalation. Defines budgets (error budget, latency budget), monitors them, and fires alerts when thresholds are breached. Supports severity escalation: warn → page → incident. Competitors: PagerDuty, Grafana Alerting.\",\"categories\":[\"observability\",\"assurance\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":8,\"nodeLabel\":\"assurance\",\"owner\":\"mzizi\",\"collection\":\"observability\"},{\"name\":\"mzizi-api-probe\",\"type\":\"registry:lib\",\"title\":\"mzizi API Probe\",\"description\":\"API endpoint health checker. Pings all Mzizi registry API endpoints (from component_backlinks) and reports status codes, latency, and error rates. Feeds into mzizi-platform-health. Uses the backlinks system built in v4.8.0.\",\"categories\":[\"observability\",\"assurance\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":8,\"nodeLabel\":\"assurance\",\"owner\":\"mzizi\",\"collection\":\"observability\"},{\"name\":\"mzizi-application-tracker\",\"type\":\"registry:ui\",\"title\":\"mzizi Application Tracker\",\"description\":\"Branded job application status timeline showing application stages (applied, reviewed, interview, offer, rejected) with timestamps and employer feedback. Gold mineral accent. Used in Jobs dashboard.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-article-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Article Card\",\"description\":\"News article card extending mzizi-listing-card with factcheck badge, source attribution, journalist byline, and read time. Maps to news.news_article (67 columns) and news.feed_source. Used in Mukoko News feeds, Pulse aggregation, and content discovery.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-listing-card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-auth-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Auth Card\",\"description\":\"Inline sign-in and registration rendered inside an AI surface (Claude, Claude Code, any MCP client) — never a browser tab. Drives the MCP inline-auth handoff (OAuth 2.1, Dynamic Client Registration, elicitation) and takes its accent from --brand-accent, so it carries the host brand's mineral.\",\"categories\":[\"agentic-components\",\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/utils\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"agentic-components\"},{\"name\":\"mzizi-auth-layout\",\"type\":\"registry:ui\",\"title\":\"mzizi Auth Layout\",\"description\":\"Default auth screen layout that services all login, signup, and onboarding flows. Provides the branded container: centered card with mineral gradient background, Mukoko logo, language selector, and legal links. Individual auth flows (login-01..05, signup-01..05) render inside this layout as children.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"mzizi-avatar-stack\",\"type\":\"registry:ui\",\"title\":\"mzizi Avatar Stack\",\"description\":\"Overlapping +N social-proof avatar stack — the going/attendee cue. Ring-separated avatars overlap with negative margin, each an image or an initials fallback, showing up to `max` before a +N overflow bubble, with an optional trailing <total> <label> in 13px muted. Purely presentational and server-safe (no harness, no client directive) so it renders inside React Server Components. The group carries an accessible summary label (e.g. \\\"128 going\\\") while the decorative avatars are aria-hidden. sm / md sizes.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-badge-display\",\"type\":\"registry:ui\",\"title\":\"mzizi Badge Display\",\"description\":\"Ubuntu achievement badge showcase. Displays earned badges in a grid or inline strip with mineral-colored badge icons, earned dates, and rarity indicators. Maps to ubuntu.badges and ubuntu.user_badges tables. Used on profile pages, leaderboards, and achievement notifications.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/tooltip\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-balance-display\",\"type\":\"registry:ui\",\"title\":\"mzizi Balance Display\",\"description\":\"Token balance display card for the Mukoko wallet. Shows primary balance with MIT currency, secondary fiat conversion, and quick action buttons (Send, Receive, Swap). Maps to wallet.balance table. Used at the top of wallet views and in mini wallet widgets.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-base\",\"type\":\"registry:base\",\"title\":\"mzizi Base\",\"description\":\"The whole Mzizi design system as one installable foundation. `shadcn init` writes the project configuration — new-york style, lucide icons, neutral base, the @/ aliases every component imports through — installs the runtime packages the registry depends on, and pulls N1's 324 design tokens via mzizi-tokens. Start a project with this and every one of the other 573 components installs into a project already shaped to receive it.\",\"categories\":[\"styling-libs\"],\"dependencies\":[\"class-variance-authority\",\"clsx\",\"tailwind-merge\",\"lucide-react\",\"tw-animate-css\",\"radix-ui\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-tokens\",\"https://api.mzizi.dev/v1/ui/utils\"],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-bottom-nav\",\"type\":\"registry:ui\",\"title\":\"mzizi Bottom Nav\",\"description\":\"Mobile-only bottom navigation bar for Nyuchi ecosystem apps. Supports a center FAB (floating action button) — a raised button in the app mineral accent colour with a glow shadow. The center FAB is the primary creation entry point, making the \\\"create\\\" action instantly recognizable across all apps.\",\"categories\":[\"shell\"],\"dependencies\":[\"next\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"mzizi-calendar\",\"type\":\"registry:ui\",\"title\":\"mzizi Calendar\",\"description\":\"Brand calendar component wrapping react-day-picker with Mukoko event dot indicators, mineral-colored day highlights, and integrated agenda view for the selected day. Used by nhimbe (events), planner, and any Mukoko app that shows date-anchored content. The event dots are the key differentiator — small mineral-colored circles beneath dates that have associated content.\",\"categories\":[\"brand\"],\"dependencies\":[\"react-day-picker\",\"date-fns\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/calendar\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-chain-gate\",\"type\":\"registry:ui\",\"title\":\"mzizi Chain Gate\",\"description\":\"Blockchain network verification gate. Checks which chain the user is connected to before rendering chain-dependent features. Supports Nyuchi Network (Cosmos SDK), Polygon, and Ethereum. Shows branded switch-network CTA with chain logos.\",\"categories\":[\"safety\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":4,\"nodeLabel\":\"safety\",\"owner\":\"mzizi\",\"collection\":\"safety\"},{\"name\":\"mzizi-chain-resilience\",\"type\":\"registry:ui\",\"title\":\"mzizi Chain Resilience\",\"description\":\"Blockchain RPC failure handler. Handles chain disconnections, RPC timeouts, reorganizations, and wallet disconnections without crashing the UI. Auto-retries with exponential backoff. Shows branded chain status with recovery actions.\",\"categories\":[\"resilience\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":5,\"nodeLabel\":\"resilience\",\"owner\":\"mzizi\",\"collection\":\"resilience\"},{\"name\":\"mzizi-changelog-renderer\",\"type\":\"registry:ui\",\"title\":\"mzizi Changelog Renderer\",\"description\":\"Changelog renderer for N10. Reads from the public.changelog table and renders a visual timeline of releases with affected ecosystem nodes (colour-coded by axis), components added/modified/deprecated, and version labels.\",\"categories\":[\"documentation-engine\",\"documentation\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":10,\"nodeLabel\":\"documentation\",\"owner\":\"nyuchi\",\"collection\":\"documentation-engine\"},{\"name\":\"mzizi-chaos\",\"type\":\"registry:lib\",\"title\":\"mzizi Chaos\",\"description\":\"Reactive chaos diagnostics — N8 Assurance (Z-axis/depth). Runs through the entire 3D architecture, able to inject failures at any component on any horizontal layer, through any vertical spine. Two modes: (1) Injection — random error/latency at configurable probability per layer intersection. (2) Reactive — when a real error is caught anywhere in the 3D space, probe adjacent systems for blast radius. The depth dimension that proves X and Y axes work.\",\"categories\":[\"observability\",\"assurance\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":8,\"nodeLabel\":\"assurance\",\"owner\":\"mzizi\",\"collection\":\"observability\"},{\"name\":\"mzizi-command-palette\",\"type\":\"registry:ui\",\"title\":\"mzizi Command Palette\",\"description\":\"Global command palette (Cmd+K / Ctrl+K). Search across mini-apps, navigate to pages, run actions. Competitors: Vercel, Linear, Raycast. Overlay with fuzzy search, recent items, and categorized results.\",\"categories\":[\"shell\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"mzizi-commute-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Commute Card\",\"description\":\"Branded saved commute card showing a recurring route with estimated time, mode, and next departure. Gold mineral accent. Used in Transport home screen for quick access to daily commutes and in Planner as part of the daily schedule view. Renders inside MziziListView feeds.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-conformity-check\",\"type\":\"registry:lib\",\"title\":\"mzizi Conformity Check\",\"description\":\"Conformity monkey — verifies deployed components match the design registry. Scans the DOM for data-slot/data-portal attributes and cross-references with the component database. Flags: unregistered components, outdated versions, missing backlinks, components using deprecated patterns. Inspired by Netflix Conformity Monkey.\",\"categories\":[\"observability\",\"assurance\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":8,\"nodeLabel\":\"assurance\",\"owner\":\"mzizi\",\"collection\":\"observability\"},{\"name\":\"mzizi-connectivity-bar\",\"type\":\"registry:ui\",\"title\":\"mzizi Connectivity Bar\",\"description\":\"Network connectivity status bar. Shows connection state: online, syncing, cached, offline. Auto-hides when online. Uses L1 connection tokens and L5 offline-banner pattern. Placed at top of app by root layout.\",\"categories\":[\"shell\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"mzizi-content-composer\",\"type\":\"registry:ui\",\"title\":\"mzizi Content Composer\",\"description\":\"Universal freeform content creation component — the branded way to compose posts, updates, stories, and messages across the ecosystem. Combines text input, media attachment, mention tagging, and mineral-accented submit button. Used in Circles (posts), Pulse (updates), Campfire (messages), Novels (chapter drafts), and anywhere users create unstructured content. Distinct from mzizi-create-listing which handles structured form-based creation.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/textarea\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-content-gate\",\"type\":\"registry:ui\",\"title\":\"mzizi Content Gate\",\"description\":\"Content access gate with age verification, location restrictions, and content sensitivity filters. Renders children when all checks pass. Shows a branded explanation with the specific restriction reason when blocked. Supports multiple gate types that can be composed.\",\"categories\":[\"safety\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/utils\"],\"node\":4,\"nodeLabel\":\"safety\",\"owner\":\"mzizi\",\"collection\":\"safety\"},{\"name\":\"mzizi-conversation-row\",\"type\":\"registry:ui\",\"title\":\"mzizi Conversation Row\",\"description\":\"Chat conversation list row for Campfire messaging. Shows avatar, conversation name, last message preview, timestamp, unread count badge, and online status dot. Maps to campfire.conversation and campfire.participant tables. Used in the main chat list view.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-cover-header\",\"type\":\"registry:ui\",\"title\":\"mzizi Cover Header\",\"description\":\"Cover image header with overlapping avatar or logo. Used by individual and org profile pages. Branded with mineral accents and verification badge slot. Extracted from individual-profile-page and org-profile-page.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-cover-wash-header\",\"type\":\"registry:ui\",\"title\":\"mzizi Cover Wash Header\",\"description\":\"Event/detail header composite that derives the whole page tint and CTA colour from its cover — the 4.2.0 cover-wash signature. A full-bleed image or gradient cover with a legibility scrim carries an overlaid title, optional kicker/subtitle, a small inline meta row (date · location · host) and a CTA slot (children). From an accent (or mineral) prop it emits --event-primary and --wash as inline custom properties on the root, seeding the wash context that every descendant — and the rest of the detail page — inherits. Accent defaults to --brand-accent so each app swaps its own mineral; harness-wired for reduced-motion entry and observability.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-create-listing\",\"type\":\"registry:ui\",\"title\":\"mzizi Create Listing\",\"description\":\"Universal create/edit form layout for all Mukoko content types — events, products, news, bush trade listings, directories. Provides the standardized brand creation flow: cover theme picker, grouped form sections in cards, toggle/dropdown rows, description editor, and sticky publish CTA. Every \\\"Create\\\" or \\\"Edit\\\" screen across the super app uses this composition.\",\"categories\":[\"brand\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/switch\",\"https://api.mzizi.dev/v1/ui/select\",\"https://api.mzizi.dev/v1/ui/textarea\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/separator\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-create-page\",\"type\":\"registry:ui\",\"title\":\"mzizi Create Page\",\"description\":\"Standard creation flow page orchestrator. Header with cancel/title/submit, stepped or single-form layout, cover theme picker slot, section cards, and sticky submit CTA. Every creation screen uses this: create event, create listing, create post, write chapter, post video caption. The developer provides form sections as children and handles submission.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/mzizi-create-listing\",\"https://api.mzizi.dev/v1/ui/stepper\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"mzizi-credential-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Credential Card\",\"description\":\"Professional credential display card showing qualification name, issuing body, status (active/expired/revoked/suspended/pending), expiry date, and verification tier. Maps to identity.credential (35 columns) and system.regulatory_body. Used on profile pages, professional directory, and credential verification screens.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-crypto-fallback\",\"type\":\"registry:ui\",\"title\":\"mzizi Crypto Fallback\",\"description\":\"Post-quantum cryptography fallback handler. When PQC verification fails (library not loaded, algorithm not supported on device), falls back to classical verification with a \\\"reduced security\\\" visual indicator. Based on the QuantZen overlay pattern — adds quantum-safe layer without breaking existing UX.\",\"categories\":[\"resilience\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":5,\"nodeLabel\":\"resilience\",\"owner\":\"mzizi\",\"collection\":\"resilience\"},{\"name\":\"mzizi-crypto-gate\",\"type\":\"registry:ui\",\"title\":\"mzizi Crypto Gate\",\"description\":\"Quantum-safe cryptographic verification gate. Validates signature strength — in quantum-ready mode requires PQC dual-signature (CRYSTALS-Dilithium + classical ECDSA). Degrades gracefully showing \\\"reduced security\\\" indicator when only classical signatures are available. Based on the QuantZen dual-signature pattern.\",\"categories\":[\"safety\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":4,\"nodeLabel\":\"safety\",\"owner\":\"mzizi\",\"collection\":\"safety\"},{\"name\":\"mzizi-dashboard-layout\",\"type\":\"registry:ui\",\"title\":\"mzizi Dashboard Layout\",\"description\":\"Full app shell composing MziziHeader, MziziSidebar, and MziziBottomNav into a responsive dashboard layout.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-sidebar\",\"https://api.mzizi.dev/v1/ui/mzizi-header\",\"https://api.mzizi.dev/v1/ui/mzizi-bottom-nav\",\"https://api.mzizi.dev/v1/ui/sidebar\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"mzizi-data\",\"type\":\"registry:lib\",\"title\":\"mzizi Data\",\"description\":\"Data fetching and state management bridge between the 7-layer architecture and the UI. Exports useMziziQuery hook (local-first fetch: try SQLite/IndexedDB → edge D1 → cloud Supabase, with automatic background sync), MziziListView (declarative feed composition: listing-card + infinite scroll + pull-to-refresh + empty state + skeleton + error card), MziziSearchView (search input + filter bar + results + recent searches), and optimistic update utilities. Every data-driven screen in every Nyuchi app uses these patterns instead of raw fetch calls. Target: Rust compiled to WebAssembly. wasm-bindgen exposes the interface to SvelteKit. One Rust codebase compiles to both WASM (browser) and native binary (server).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-listing-card\",\"https://api.mzizi.dev/v1/ui/mzizi-skeleton-set\",\"https://api.mzizi.dev/v1/ui/mzizi-error-set\",\"https://api.mzizi.dev/v1/ui/mzizi-a11y\"],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-deep-link-handler\",\"type\":\"registry:ui\",\"title\":\"mzizi Deep Link Handler\",\"description\":\"Universal link and deep link routing handler. Maps external URLs, push notification taps, and share links to in-app routes. Handles: mukoko.com/{path}, mini-app deep links, push notification payloads, NFC/QR scan results.\",\"categories\":[\"shell\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"mzizi-degradation-chain\",\"type\":\"registry:ui\",\"title\":\"mzizi Degradation Chain\",\"description\":\"Configurable degradation fallback chain: personalized → cached → trending → default → static. Each level renders when the previous fails. Based on Netflix graceful degradation — the \\\"bookmark service\\\" pattern applied across all mini-apps.\",\"categories\":[\"resilience\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":5,\"nodeLabel\":\"resilience\",\"owner\":\"mzizi\",\"collection\":\"resilience\"},{\"name\":\"mzizi-detail-layout\",\"type\":\"registry:ui\",\"title\":\"mzizi Detail Layout\",\"description\":\"Shared detail page layout for all content types across the Nyuchi ecosystem — events, articles, products, bush trade listings, places. Supports a hero gradient variant with floating back/like/share action buttons, mineral-colored category badges, and verified badge display for the host/author. The detail-layout is the brand-standard way to present any single piece of content at full depth.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/separator\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"mzizi-detail-page\",\"type\":\"registry:ui\",\"title\":\"mzizi Detail Page\",\"description\":\"Standard detail page orchestrator. Hero section with cover image, back/share/like actions, content body, related items section, and sticky bottom CTA. Every content detail screen uses this: event detail, article detail, product detail, place detail, job detail, profile detail, novel detail. The developer provides the hero content, body sections, and action handlers.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/mzizi-detail-layout\",\"https://api.mzizi.dev/v1/ui/mzizi-share-card\",\"https://api.mzizi.dev/v1/ui/mzizi-action-sheet\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"mzizi-did-gate\",\"type\":\"registry:ui\",\"title\":\"mzizi Did Gate\",\"description\":\"Decentralized Identifier (DID) credential gate. Verifies W3C Verifiable Credentials from the Nyuchi Honeycomb pod. Works offline by checking the local credential store first, then validates against the server when connectivity returns. Shows \\\"locally verified\\\" vs \\\"server confirmed\\\" status.\",\"categories\":[\"safety\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":4,\"nodeLabel\":\"safety\",\"owner\":\"mzizi\",\"collection\":\"safety\"},{\"name\":\"mzizi-docs-api\",\"type\":\"registry:lib\",\"title\":\"mzizi Docs API\",\"description\":\"Documentation read API for N10. Runs as a Supabase Deno edge function (`Deno.serve`, `jsr:@supabase/supabase-js@2`) — NOT a Cloudflare Worker and not Rust/WASM, which is what this description claimed while the file said otherwise. Serves documentation pages, AI instructions, architecture nodes and changelog entries over PostgREST: /docs, /ai-instructions, /architecture, /changelog, /counts. Public read, no auth. Deno-runtime source, so it is excluded from tsconfig and is not installable into a React app via the shadcn CLI.\",\"categories\":[\"documentation-engine\",\"documentation\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":10,\"nodeLabel\":\"documentation\",\"owner\":\"nyuchi\",\"collection\":\"documentation-engine\"},{\"name\":\"mzizi-docs-engine\",\"type\":\"registry:ui\",\"title\":\"mzizi Docs Engine\",\"description\":\"Documentation rendering engine for N10. Full-screen docs portal with sidebar navigation, search (title + description + keywords), MDX-delegate content rendering, related ecosystem node badges, and keyboard navigation. Reads from documentation_pages via mzizi-docs-api.\",\"categories\":[\"documentation-engine\",\"documentation\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":10,\"nodeLabel\":\"documentation\",\"owner\":\"nyuchi\",\"collection\":\"documentation-engine\"},{\"name\":\"mzizi-dx\",\"type\":\"registry:lib\",\"title\":\"mzizi DX\",\"description\":\"Developer experience infrastructure for the Nyuchi Design System. Exports component status definitions (alpha/beta/stable/deprecated with visual indicators), component documentation schema (usage guidelines, do/dont examples, accessibility notes, keyboard interactions, screen reader behavior stored in component_docs table), contribution guidelines (how to propose, build, review, and ship new brand components), migration utilities (deprecation warnings, codemod helpers), and usage analytics types (which components are used, by which apps, how frequently). Also exports a ComponentPlayground wrapper for interactive component previews. Target: Rust compiled to WebAssembly. wasm-bindgen exposes the interface to SvelteKit. One Rust codebase compiles to both WASM (browser) and native binary (server).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-tokens\"],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-empty-screen\",\"type\":\"registry:ui\",\"title\":\"mzizi Empty Screen\",\"description\":\"Default empty state screen for pages with no content yet. Used across mini-apps when a feed, list, or section has zero items. Branded illustration placeholder, message, and optional CTA.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"mzizi-empty-state\",\"type\":\"registry:ui\",\"title\":\"mzizi Empty State\",\"description\":\"Universal branded empty state with dynamic mineral accent, contextual illustration placeholder, title, description, and optional action CTA. Every screen across the ecosystem uses this when there is no content to display — \\\"No events yet\\\", \\\"Your wallet is empty\\\", \\\"No messages\\\", \\\"Start your first lesson\\\". The empty state guides users toward the creation action for that domain.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/empty\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-error-screen\",\"type\":\"registry:ui\",\"title\":\"mzizi Error Screen\",\"description\":\"Default full-page error screen for 404, 500, 503, and custom errors. Used by the App Router error.tsx and not-found.tsx pages. Branded with mineral gradient, retry action, and navigation back to safety.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"mzizi-error-set\",\"type\":\"registry:ui\",\"title\":\"mzizi Error Set\",\"description\":\"Brand error and resilience states for the Nyuchi ecosystem. Exports ErrorCard (inline error within feeds), NotFound (brand 404 page), OfflineBanner (local-first connection status indicator), SectionFallback (section-level error with retry). NOTE: PermissionGate is also currently exported here but architecturally belongs at Layer 4 (Safety & Trust) and should be extracted into a separate component in a future refactor. Each state uses mineral accents, brand typography, and matches the Nyuchi identity system.\",\"categories\":[\"resilience\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":5,\"nodeLabel\":\"resilience\",\"owner\":\"mzizi\",\"collection\":\"resilience\"},{\"name\":\"mzizi-error-tracker\",\"type\":\"registry:lib\",\"title\":\"mzizi Error Tracker\",\"description\":\"Structured error collection, deduplication, and blast radius analysis. Catches errors from L5 error boundaries, enriches them with component backlinks (data-portal), calculates which mini-apps and layers are affected. Competitors: Sentry, Datadog Error Tracking.\",\"categories\":[\"observability\",\"assurance\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":8,\"nodeLabel\":\"assurance\",\"owner\":\"mzizi\",\"collection\":\"observability\"},{\"name\":\"mzizi-escalation-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Escalation Card\",\"description\":\"The UCP Embedded Checkout Protocol \\\"requires escalation\\\" mini-card: when an agentic flow needs a human choice, the agent pauses and renders this inline, then resumes on selection. Keeps a decision point in the conversation instead of handing off to a browser.\",\"categories\":[\"agentic-components\",\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/utils\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"agentic-components\"},{\"name\":\"mzizi-event-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Event Card\",\"description\":\"DEPRECATED: Use mzizi-ticket-card instead. This was an early pre-brand event card without harness, motion, or ARIA integration.\",\"categories\":[\"brand\"],\"dependencies\":[\"lucide-react\"],\"registryDependencies\":[],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-feature-gate\",\"type\":\"registry:ui\",\"title\":\"mzizi Feature Gate\",\"description\":\"Feature flag gate. Instantly show/hide features based on remote config without deployment. Supports kill switch for instant feature disable. Based on Netflix feature flag pattern + OpenFeature standard. Checks local flag cache first (device-first), then remote config.\",\"categories\":[\"safety\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":4,\"nodeLabel\":\"safety\",\"owner\":\"mzizi\",\"collection\":\"safety\"},{\"name\":\"mzizi-featured-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Featured Card\",\"description\":\"DEPRECATED: Use mzizi-listing-card instead. This was an early pre-brand featured listing without harness, motion, or ARIA integration.\",\"categories\":[\"brand\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-feed-page\",\"type\":\"registry:ui\",\"title\":\"mzizi Feed Page\",\"description\":\"Standard feed page orchestrator composing header, search/filter bar, MziziListView infinite scroll feed, pull-to-refresh, empty state, and FAB create button into a complete screen. Every feed screen across every mini-app uses this layout — Pulse home, News feed, Circles timeline, BushTrade listings, Jobs board, Nhimbe events, Places directory. The developer provides the data source, card renderer, and filter configuration. The orchestrator handles layout, loading, errors, and responsive behaviour.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/mzizi-listing-card\",\"https://api.mzizi.dev/v1/ui/mzizi-empty-state\",\"https://api.mzizi.dev/v1/ui/filter-bar\",\"https://api.mzizi.dev/v1/ui/infinite-scroll\",\"https://api.mzizi.dev/v1/ui/pull-to-refresh\",\"https://api.mzizi.dev/v1/ui/mzizi-skeleton-set\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"mzizi-footer\",\"type\":\"registry:ui\",\"title\":\"mzizi Footer\",\"description\":\"Standardized footer for all Nyuchi ecosystem apps with mineral color dots.\",\"categories\":[\"shell\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/separator\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"mzizi-forecast-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Forecast Card\",\"description\":\"Branded weather forecast display with cobalt mineral accent. Shows current conditions, temperature, humidity, 5-day outlook, and farming/agriculture advisory for African rural contexts. Mineral-coded severity levels for weather alerts. Used in Weather home and as an embedded widget in Nhimbe event pages and BushTrade logistics.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-fundi\",\"type\":\"registry:lib\",\"title\":\"mzizi fundi\",\"description\":\"Fundi — the self-healing intelligence engine. Swahili for fixer/mechanic/technician. N9 acts on what N8 observes. Built in Rust, deployed as a Cloudflare Worker (workers-rs crate, compiled to WebAssembly). NOT a Cloudflare Worker (Rust/WASM) — Fundi is independent of the database vendor. Reads fundi_issues and observability_events from Supabase via REST API. Classifies failure patterns, deduplicates against open GitHub issues, creates structured remediation tickets. Triggered by Cloudflare Cron Triggers and Cloudflare Queues.\",\"categories\":[\"fundi\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":9,\"nodeLabel\":\"fundi\",\"owner\":\"nyuchi\",\"collection\":\"fundi\"},{\"name\":\"mzizi-fundi-learning\",\"type\":\"registry:lib\",\"title\":\"mzizi fundi Learning\",\"description\":\"Fundi learning system. Tracks healing outcomes to improve future decisions. When an issue is resolved, records what failed, what Fundi suggested, what actually worked, and whether the same pattern recurs. Runs as part of the Fundi Cloudflare Worker — same Rust binary, different handler. Writes learned patterns to fundi_issues via Supabase REST API. Improves classification accuracy over time.\",\"categories\":[\"fundi\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":9,\"nodeLabel\":\"fundi\",\"owner\":\"nyuchi\",\"collection\":\"fundi\"},{\"name\":\"mzizi-fundi-reporter\",\"type\":\"registry:lib\",\"title\":\"mzizi fundi Reporter\",\"description\":\"Bridge from N8 Assurance to N9 Fundi. When N8 components detect failures, the reporter pushes a structured signal to the Cloudflare Queue that triggers Fundi. Signal payload: component name, ecosystem node, error type, severity, diagnostic data. The Queue decouples N8 (detection) from N9 (healing) — N8 never waits for Fundi to respond. Also creates a fundi_issues row in Supabase as the append-only audit trail.\",\"categories\":[\"fundi\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":9,\"nodeLabel\":\"fundi\",\"owner\":\"nyuchi\",\"collection\":\"fundi\"},{\"name\":\"mzizi-gauge-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Gauge Card\",\"description\":\"Branded metric card with radial arc gauge, value display, label, and contextual description. The universal pattern extracted from mukoko-weather MetricCard. Used for UV index, trust scores, completion percentages, health vitals, battery levels, signal strength — any bounded numeric value displayed as a gauge. Composes the arc-gauge primitive with brand styling.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/arc-gauge\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-geo-gate\",\"type\":\"registry:ui\",\"title\":\"mzizi Geo Gate\",\"description\":\"Data sovereignty and jurisdiction gate. Ensures content and features comply with the user's local data protection laws (GDPR, POPIA, Zimbabwe DPRA). Blocks region-restricted content with branded explanation and jurisdiction info.\",\"categories\":[\"safety\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":4,\"nodeLabel\":\"safety\",\"owner\":\"mzizi\",\"collection\":\"safety\"},{\"name\":\"mzizi-grid\",\"type\":\"registry:ui\",\"title\":\"mzizi Grid\",\"description\":\"Responsive grid system with breakpoints: mobile (0-639), tablet (640-1023), desktop (1024-1439), wide (1440+). Supports column count overrides per breakpoint. Used by all page layouts to arrange sections. The architecture doc specifies this as MukokoGrid.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"mzizi-group-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Group Card\",\"description\":\"Universal branded group/community card with dynamic mineral accent based on the parent app. Shows group name, member count, avatar strip, activity indicator, privacy level, topic tags, and join CTA. Used across Circles (communities), Campfire (group chats), BushTrade (seller groups), Nhimbe (event communities), and any app with group-based features.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-harness\",\"type\":\"registry:lib\",\"title\":\"mzizi Harness\",\"description\":\"The Nyuchi component harness — the vertical infrastructure spine (useMziziHarness hook + MziziHarness wrapper) giving N3 brand, N4 safety, and N5 resilience components scoped logging, motion, and an accessible shared live region. Self-contained; adapt the console logger to your app observability layer.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/utils\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-harness-prewire\",\"type\":\"registry:lib\",\"title\":\"mzizi Harness Prewire\",\"description\":\"Pre-wiring node that connects every brand component to the full Nyuchi infrastructure stack. Exports MziziHarness (declarative wrapper providing error boundary + skeleton + observability + motion + a11y) and useMziziHarness hook (imperative access to observability logger, motion config, locale, theme, and health reporting). When a component is wrapped in a harness, it automatically logs render timing, catches errors with branded fallback, applies entry animations, uses the correct focus ring tokens, respects reduced-motion preferences, and reports health to the global monitor. Zero manual configuration required — install the component and it works. Target: Rust compiled to WebAssembly. wasm-bindgen exposes the interface to SvelteKit. One Rust codebase compiles to both WASM (browser) and native binary (server).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/observability\",\"https://api.mzizi.dev/v1/ui/mzizi-motion\",\"https://api.mzizi.dev/v1/ui/mzizi-a11y\",\"https://api.mzizi.dev/v1/ui/mzizi-tokens\",\"https://api.mzizi.dev/v1/ui/mzizi-theme-provider\",\"https://api.mzizi.dev/v1/ui/mzizi-resilience\"],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-header\",\"type\":\"registry:ui\",\"title\":\"mzizi Header\",\"description\":\"Standardized sticky header for all Nyuchi ecosystem apps. Supports desktop nav links and mobile pill action group — a capsule-shaped container with 2–3 icon buttons in the brand accent color (dynamic per mini-app). The pill group is the primary brand identity marker in the header.\",\"categories\":[\"shell\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/sidebar\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"mzizi-health-dashboard\",\"type\":\"registry:ui\",\"title\":\"mzizi Health Dashboard\",\"description\":\"Branded health overview card composing vitals-display, medication-reminder, and appointment-card primitives with malachite mineral accent. Shows key health metrics, upcoming appointments, and medication schedule at a glance. Sovereign data — all health data stays in the user pod.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/vitals-display\",\"https://api.mzizi.dev/v1/ui/appointment-card\",\"https://api.mzizi.dev/v1/ui/medication-reminder\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-hero-stat\",\"type\":\"registry:ui\",\"title\":\"mzizi Hero Stat\",\"description\":\"Branded large primary value display with condition label, secondary stats, icon, and share button. The universal pattern extracted from mukoko-weather CurrentConditions. Used for: temperature (Weather), portfolio value (Wallet), trust score (ID), daily steps (Health), total revenue (BushTrade). Any screen that needs a single large number as its hero.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-icons\",\"type\":\"registry:lib\",\"title\":\"mzizi Icons\",\"description\":\"Central icon registry for the Nyuchi Design System. Maps semantic icon names to Lucide React icons. All components should import icons from this registry, not directly from lucide-react. Provides: consistent sizing (16/20/24/32), brand icon overrides, semantic naming (icon-success vs CheckCircle), tree-shaking optimization, and custom Nyuchi icons. N1 infrastructure — consumed by all layers above. Target: Rust compiled to WebAssembly. wasm-bindgen exposes the interface to SvelteKit. One Rust codebase compiles to both WASM (browser) and native binary (server).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-incident-manager\",\"type\":\"registry:lib\",\"title\":\"mzizi Incident Manager\",\"description\":\"Incident lifecycle management: detect → triage → mitigate → resolve → postmortem. Tracks incidents with severity, affected components (via backlinks), timeline of actions, and generates postmortem templates. Competitors: PagerDuty, incident.io.\",\"categories\":[\"observability\",\"assurance\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":8,\"nodeLabel\":\"assurance\",\"owner\":\"mzizi\",\"collection\":\"observability\"},{\"name\":\"mzizi-job-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Job Card\",\"description\":\"Branded job listing card with employer verification badge, salary range, location, skill tags, and apply CTA. Gold mineral accent. Renders inside MziziListView feeds. Appears in Jobs feed, Pulse aggregation, search results, and recommended listings. The job-specific data shape (salary, skills, match score, remote flag) makes this a distinct card type rather than a mzizi-listing-card variant.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-layout\",\"type\":\"registry:lib\",\"title\":\"mzizi Layout\",\"description\":\"Responsive and adaptive layout system for Nyuchi. Exports breakpoint tokens (mobile/tablet/desktop/wide), useBreakpoint hook, MziziGrid responsive grid component, canonical layout definitions for the five core page types (feed, detail, create, profile, dashboard), and adaptive density support (comfortable vs compact). Ensures consistent responsive behavior across all Nyuchi apps — same breakpoints, same grid, same content flow patterns. Target: Rust compiled to WebAssembly. wasm-bindgen exposes the interface to SvelteKit. One Rust codebase compiles to both WASM (browser) and native binary (server).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-tokens\"],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-leaderboard-row\",\"type\":\"registry:ui\",\"title\":\"mzizi Leaderboard Row\",\"description\":\"Ranked leaderboard row with position indicator, avatar, name, score, and trend. Maps to ubuntu.leaderboards table. Used in community rankings, Ubuntu point leaderboards, and competitive features. Top 3 positions get mineral-colored rank badges (Gold, Tanzanite, Cobalt).\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-lesson-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Lesson Card\",\"description\":\"Branded language lesson card for Mukoko Lingo with cobalt mineral accent. Shows lesson title, language pair, progress percentage, streak indicator, difficulty level, and estimated time. Used in Lingo course listings and learning path displays.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/progress\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-listing-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Listing Card\",\"description\":\"Universal content card for all Mukoko listing types — events, products, news, bush trade, directories. Supports row, compact, and hero variants with mineral-colored category accents. The foundational brand component: every listing across the super app uses this pattern.\",\"categories\":[\"brand\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-load-shedder\",\"type\":\"registry:ui\",\"title\":\"mzizi Load Shedder\",\"description\":\"Prioritized load shedding. Under pressure, critical requests (auth, payment) survive while non-critical (analytics, recommendations, media enrichment) are shed first. Shows degraded mode indicator. Based on Netflix prioritized load shedding.\",\"categories\":[\"resilience\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":5,\"nodeLabel\":\"resilience\",\"owner\":\"mzizi\",\"collection\":\"resilience\"},{\"name\":\"mzizi-locale\",\"type\":\"registry:lib\",\"title\":\"mzizi Locale\",\"description\":\"Internationalization infrastructure for the Pan-African Nyuchi platform. Exports MziziLocaleProvider (React context for locale, text direction, number/date/currency formatting), useLocale hook (access formatting functions), useDirection hook (RTL/LTR detection), locale-aware formatters for dates (relative and absolute), currencies (multi-currency with ZWL/ZAR/USD/KES support), numbers, and relative time. Supports all 16 Zimbabwean official languages. String token system for UI labels with locale variants so buttons, headings, and system text can be swapped without code changes. Bridges to the lingo schema in nyuchi_platform_db. Target: Rust compiled to WebAssembly. wasm-bindgen exposes the interface to SvelteKit. One Rust codebase compiles to both WASM (browser) and native binary (server).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-tokens\"],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-media\",\"type\":\"registry:ui\",\"title\":\"mzizi Media\",\"description\":\"Brand media components for the Nyuchi ecosystem. Exports MukokoMediaViewer (full-screen image/video viewer with swipe navigation, pinch-to-zoom, share), MukokoImageGallery (multi-image grid with lightbox expansion and count badge), MukokoReaderMode (long-form content reader with adjustable font size, line height, theme, and estimated reading time for articles and novel chapters), MukokoMediaMessage (standardized format for image/video/audio/document/location messages in Campfire chat), and MukokoStoryRing (ephemeral content avatar ring with gradient border and unseen indicator). Maps to bytes.video_object, novels.chapter, news.news_article, campfire.message, and content.creative_work.\",\"categories\":[\"brand\"],\"dependencies\":[\"dompurify\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/dialog\",\"https://api.mzizi.dev/v1/ui/carousel\",\"https://api.mzizi.dev/v1/ui/lightbox\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-message-bubble\",\"type\":\"registry:ui\",\"title\":\"mzizi Message Bubble\",\"description\":\"Brand-styled chat message bubble for Campfire messaging. Supports sent/received variants, delivery status indicators (sent/delivered/read), emoji reactions, reply threading, and verified sender badge. Maps to campfire.message and campfire.reaction tables. The Mukoko chat identity: pill-shaped bubbles with malachite accent on sent messages.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-meta-tile\",\"type\":\"registry:ui\",\"title\":\"mzizi Meta Tile\",\"description\":\"Universal date/location signature tile — the 4.2.0 When/Where unit. A rounded-square chip (an icon tile OR a month-over-day date chip) paired with a bold 16px primary line and a 13px muted secondary line, with an optional caption and trailing action. Purely presentational and server-safe (no client directive, no harness) so it renders inside React Server Components. The chip tint defaults to the brand accent (--brand-accent) so each app swaps its own mineral. Used on event detail When/Where rows, place cards, and any listing that anchors a date or a location.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-mini-app-runtime\",\"type\":\"registry:ui\",\"title\":\"mzizi Mini App Runtime\",\"description\":\"Mini-app container for Mukoko super-app architecture. Manages mini-app lifecycle: load, mount, suspend, destroy. Provides isolated context per mini-app with shared token system. Architecture doc: \\\"13 Tier 2 web mini-apps\\\" + \\\"4 Tier 1 native mini-apps\\\".\",\"categories\":[\"shell\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"mzizi-mission-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Mission Card\",\"description\":\"Ubuntu mission/quest card with progress bar, reward badge preview, difficulty indicator, and completion state. Maps to ubuntu.missions and ubuntu.mission_progress tables. Used in the Ubuntu gamification system to drive community contribution through structured challenges.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/progress\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-moderation-gate\",\"type\":\"registry:ui\",\"title\":\"mzizi Moderation Gate\",\"description\":\"Content moderation status gate. Blocks rendering until content passes the Shamwari AI safety pipeline. Shows branded \\\"under review\\\" state with estimated wait time. Supports on-device moderation check (CoreML/NNAPI) before cloud verification. Based on TikTok two-stage AI moderation.\",\"categories\":[\"safety\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":4,\"nodeLabel\":\"safety\",\"owner\":\"mzizi\",\"collection\":\"safety\"},{\"name\":\"mzizi-motion\",\"type\":\"registry:lib\",\"title\":\"mzizi Motion\",\"description\":\"Motion token system defining the Nyuchi animation language. Includes four duration tiers (quick/standard/emphasis/dramatic), four easing curves (entrance/exit/spring/bounce), choreography patterns (list stagger, cascade, ripple), and reduced-motion fallbacks. Respects prefers-reduced-motion at the token level. Exports CSS keyframes, React transition presets, and a useMotion hook. Target: Rust compiled to WebAssembly. wasm-bindgen exposes the interface to SvelteKit. One Rust codebase compiles to both WASM (browser) and native binary (server).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-tokens\"],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-notification-center\",\"type\":\"registry:ui\",\"title\":\"mzizi Notification Center\",\"description\":\"App-wide notification drawer/panel. Displays notifications grouped by type (social, fintech, system, alerts), with read/unread state, actions, and swipe-to-dismiss. Competitors: Instagram, Slack, WeChat.\",\"categories\":[\"shell\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"mzizi-notification-item\",\"type\":\"registry:ui\",\"title\":\"mzizi Notification Item\",\"description\":\"Brand notification item for the Mukoko notification center. Displays a notification with action type icon, actor avatar, message, timestamp, and read/unread state. Mineral-colored icon backgrounds map to notification categories. Used across all Mukoko apps via the shared system.notifications schema.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-offer-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Offer Card\",\"description\":\"Commerce offer/product card for Bush Trade with price display, original/discount pricing, availability, seller verification badge, and inquiry CTA. Maps to commerce.offer and commerce.product tables. Used in Bush Trade marketplace, commerce listings, and deal displays.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-listing-card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-offline-gate\",\"type\":\"registry:ui\",\"title\":\"mzizi Offline Gate\",\"description\":\"Offline feature availability gate. Determines if a feature works offline, with cached data, or requires a connection. Shows branded \\\"available offline\\\" or \\\"requires connection\\\" state. Critical for local-first architecture — treats connectivity as a gradient, not a binary. Based on WhatsApp offline queuing but for all feature types.\",\"categories\":[\"safety\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":4,\"nodeLabel\":\"safety\",\"owner\":\"mzizi\",\"collection\":\"safety\"},{\"name\":\"mzizi-onboarding-step\",\"type\":\"registry:ui\",\"title\":\"mzizi Onboarding Step\",\"description\":\"Universal branded onboarding step card used across all ecosystem app first-use experiences. Shows an illustration area, title, description, progress dots, and next/skip actions. Dynamic mineral accent. The individual step within any onboarding flow — composable with swiper or stepper primitives for multi-step sequences.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/stepper\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-otel\",\"type\":\"registry:lib\",\"title\":\"mzizi Otel\",\"description\":\"OTLP exporter for N8 assurance signals. Turns probe results, tracked errors and fired alerts into OpenTelemetry spans and POSTs them to any OTLP/HTTP collector, so a Mzizi signal can be consumed by any agent or service that speaks OpenTelemetry. Zero dependencies and no Node built-ins - runs unchanged in Node, a Cloudflare Worker, Deno or a browser.\",\"categories\":[\"observability\",\"assurance\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-synthetic-probe\"],\"node\":8,\"nodeLabel\":\"assurance\",\"owner\":\"mzizi\",\"collection\":\"observability\"},{\"name\":\"mzizi-page\",\"type\":\"registry:ui\",\"title\":\"mzizi Page\",\"description\":\"Canonical page wrapper with SEO metadata, Open Graph, Schema.org structured data, and canonical layouts (feed, detail, settings, dashboard, auth). Architecture doc: MukokoPage. Every screen in the app is wrapped in MziziPage which handles head metadata, breadcrumbs, and the page-level section arrangement.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"mzizi-payment-mandate-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Payment Mandate Card\",\"description\":\"Inline payment authorization rendered in the AI client. Selects a payment handler (rail) and authorizes an AP2 Payment Mandate — no browser checkout, and no card details leaving the wallet.\",\"categories\":[\"agentic-components\",\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/utils\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"agentic-components\"},{\"name\":\"mzizi-payment-summary\",\"type\":\"registry:ui\",\"title\":\"mzizi Payment Summary\",\"description\":\"Universal branded payment summary and confirmation card. Shows what is being purchased, price breakdown, payment method, and confirm CTA. Used across all domains with financial transactions: BushTrade (product purchase), Nhimbe (ticket purchase), Transport (ride booking), Wallet (token transfer), Health (consultation fee), Novels (chapter unlock). Supports multi-currency (ZWL, USD, ZAR, KES) and MIT token payments.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-perf-probe\",\"type\":\"registry:lib\",\"title\":\"mzizi Perf Probe\",\"description\":\"Web Vitals and performance monitoring. Tracks LCP, FID, CLS, INP, TTFB per page and per component (using data-portal backlinks). Reports to analytics. Competitors: Lighthouse, Web Vitals library, Vercel Analytics.\",\"categories\":[\"observability\",\"assurance\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":8,\"nodeLabel\":\"assurance\",\"owner\":\"mzizi\",\"collection\":\"observability\"},{\"name\":\"mzizi-permission-gate\",\"type\":\"registry:ui\",\"title\":\"mzizi Permission Gate\",\"description\":\"Verification tier gate. Checks the user's verification tier against the required tier before rendering children. Shows a branded mineral-colored upgrade CTA if insufficient. Maps to system.verification_tier (5 tiers: unverified, community, OTP, government, licensed). Renders children when check passes, branded explanation when it doesn't.\",\"categories\":[\"safety\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/utils\"],\"node\":4,\"nodeLabel\":\"safety\",\"owner\":\"mzizi\",\"collection\":\"safety\"},{\"name\":\"mzizi-persistent-player\",\"type\":\"registry:ui\",\"title\":\"mzizi Persistent Player\",\"description\":\"Media player that persists across navigation. Mini-player bar at bottom when navigating away from media. Supports video, audio, and live streams. Competitors: YouTube mini-player, Spotify persistent bar, Netflix PiP.\",\"categories\":[\"shell\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"mzizi-phrase-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Phrase Card\",\"description\":\"Branded word/phrase learning card for Mukoko Lingo with cobalt mineral accent. Shows phrase in source and target languages, pronunciation guide, usage example, and practice/audio controls. Part of the commitment to treating African languages as first-class citizens.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-place-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Place Card\",\"description\":\"Place/location card extending mzizi-listing-card with map pin, verification badge, distance, rating, and opening hours. Maps to places.places (104 columns). Used in Mukoko Places, Shamwari location suggestions, hospitality listings, and any app that displays a physical location.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-listing-card\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\",\"https://api.mzizi.dev/v1/ui/rating\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-platform\",\"type\":\"registry:lib\",\"title\":\"mzizi Platform\",\"description\":\"Platform-specific utilities for PWA, native integration, and cross-platform behavior. Exports MziziInstallPrompt (branded PWA \\\"Add to Home Screen\\\" experience), MziziShareSheet (Web Share API with fallback), usePlatform hook (detect iOS/Android/web/PWA), useHaptics (haptic feedback with graceful degradation), and platform-aware navigation patterns. Makes every Nyuchi app feel native regardless of how it is accessed. Target: Rust compiled to WebAssembly. wasm-bindgen exposes the interface to SvelteKit. One Rust codebase compiles to both WASM (browser) and native binary (server).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/mzizi-tokens\"],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-platform-health\",\"type\":\"registry:ui\",\"title\":\"mzizi Platform Health\",\"description\":\"Transparent platform health dashboard — N8 Assurance (Z-axis/depth). Observes the full 3D architecture: every horizontal layer (L2-L7), every vertical spine (L1 tokens, L4 safety, L5 resilience), and reports which intersections are operational, degraded, or in outage. The depth view into the system.\",\"categories\":[\"observability\",\"assurance\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":8,\"nodeLabel\":\"assurance\",\"owner\":\"mzizi\",\"collection\":\"observability\"},{\"name\":\"mzizi-prefetch-boundary\",\"type\":\"registry:ui\",\"title\":\"mzizi Prefetch Boundary\",\"description\":\"Pre-loads the next screen critical data and components while user is on current screen. TikTok seamless swipe pattern for all navigation. Respects Save-Data header and low battery.\",\"categories\":[\"resilience\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":5,\"nodeLabel\":\"resilience\",\"owner\":\"mzizi\",\"collection\":\"resilience\"},{\"name\":\"mzizi-product-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Product Card\",\"description\":\"DEPRECATED: Use mzizi-offer-card instead. This was an early pre-brand product card without harness, motion, or ARIA integration.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-product-results\",\"type\":\"registry:ui\",\"title\":\"mzizi Product Results\",\"description\":\"The top 3–5 UCP product search results rendered inline in an AI surface, so a buyer can compare and choose without leaving the conversation.\",\"categories\":[\"agentic-components\",\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/utils\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"agentic-components\"},{\"name\":\"mzizi-profile-block\",\"type\":\"registry:ui\",\"title\":\"mzizi Profile Block\",\"description\":\"Standardized profile display block used across all Mukoko apps. Centers the avatar with verification badge, name with inline verified check, platform status indicator, trust score meter, and stat counters. The three trust indicators (verified badge, status pill, trust score) are always visible on the profile header — not just badges elsewhere in the app, but the full expanded view here.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/separator\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/progress\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-profile-header\",\"type\":\"registry:ui\",\"title\":\"mzizi Profile Header\",\"description\":\"Full-width profile banner with avatar, name, bio, stats, and actions.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-profile-page\",\"type\":\"registry:block\",\"title\":\"mzizi Profile Page\",\"description\":\"Standard profile page with cover image, avatar, stats, tabs, and action buttons.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/separator\",\"https://api.mzizi.dev/v1/ui/tabs\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-profile-page-layout\",\"type\":\"registry:ui\",\"title\":\"mzizi Profile Page Layout\",\"description\":\"Standard profile page orchestrator with cover image, avatar, name/verification display, stat counters, tab navigation, and content area. Every user, business, organisation, and group profile uses this layout across the ecosystem. The developer provides the tab content as children.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/mzizi-profile-block\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\",\"https://api.mzizi.dev/v1/ui/tabs\",\"https://api.mzizi.dev/v1/ui/avatar\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"mzizi-profile-settings\",\"type\":\"registry:block\",\"title\":\"mzizi Profile Settings\",\"description\":\"Standard settings page with sidebar navigation and content sections.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\",\"https://api.mzizi.dev/v1/ui/separator\",\"https://api.mzizi.dev/v1/ui/switch\",\"https://api.mzizi.dev/v1/ui/textarea\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-programme-item\",\"type\":\"registry:ui\",\"title\":\"mzizi Programme Item\",\"description\":\"Timeline-style programme/schedule item for event agendas. Shows time slot, title, speaker/host, and mineral-colored timeline dot. Maps to events.programme_item table. Used on event detail pages for agenda/programme display.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-provider-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Provider Card\",\"description\":\"Branded service provider card showing a professional or business with verification badge, specialty, availability, rating, and booking CTA. Dynamic mineral accent. Used across Health (doctors), Places (businesses), BushTrade (sellers), and any domain where verified service providers need display. Renders inside MziziListView feeds.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-rate-gate\",\"type\":\"registry:ui\",\"title\":\"mzizi Rate Gate\",\"description\":\"Client-side rate limiting gate. Shows \\\"too many attempts\\\" with cooldown timer BEFORE requests leave the device. Prevents unnecessary server load and gives immediate feedback. Based on WeChat gateway rate limiting pattern.\",\"categories\":[\"safety\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":4,\"nodeLabel\":\"safety\",\"owner\":\"mzizi\",\"collection\":\"safety\"},{\"name\":\"mzizi-registration-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Registration Card\",\"description\":\"Ticket registration panel — a labelled header, a selectable ticket-tier list, a clamped quantity stepper, and one saturated accent CTA. Tiers render as radios (role=radio / aria-checked) showing name + USD-formatted price (Free for 0) and disable with a Sold out label; the Minus/Plus stepper uses 48px pill targets and clamps to [min, max]. The accent CTA (pill, --brand-accent) shows the running total and a loading spinner. Harness-wired: tier and quantity changes are announced through the live region. Accent defaults to --brand-accent so each app swaps its own mineral.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-resilience\",\"type\":\"registry:lib\",\"title\":\"mzizi Resilience\",\"description\":\"Self-monitoring, self-healing resilience node that wires observability, circuit-breaker, bulkhead, rate-limiter, retry, timeout, fallback-chain, and chaos testing into a single composition for brand components. Implements all 7 Resilience4j patterns (Netflix Hystrix successor): CircuitBreaker, Retry, RateLimiter, Bulkhead, TimeLimiter, FallbackChain, and ChaosInjection. Exports MziziSection (wraps any UI section with error boundary + skeleton + observability + health tracking), useResilientFetch (data fetching with full resilience stack), MziziHealthMonitor (singleton aggregating health across all sections), MziziHealthPanel (admin health visualization), and the withResilience decorator that composes all patterns in the correct order. Every brand component renders inside a MziziSection so the infrastructure permanently monitors itself. Target: Rust compiled to WebAssembly. wasm-bindgen exposes the interface to SvelteKit. One Rust codebase compiles to both WASM (browser) and native binary (server).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/observability\",\"https://api.mzizi.dev/v1/ui/circuit-breaker\",\"https://api.mzizi.dev/v1/ui/retry\",\"https://api.mzizi.dev/v1/ui/timeout\",\"https://api.mzizi.dev/v1/ui/fallback-chain\",\"https://api.mzizi.dev/v1/ui/bulkhead\",\"https://api.mzizi.dev/v1/ui/rate-limiter\",\"https://api.mzizi.dev/v1/ui/chaos\",\"https://api.mzizi.dev/v1/ui/section-error-boundary\",\"https://api.mzizi.dev/v1/ui/mzizi-skeleton-set\",\"https://api.mzizi.dev/v1/ui/mzizi-error-set\"],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-review-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Review Card\",\"description\":\"Universal branded review card with trust-verified reviewer badge, star rating, review text, and helpful vote. Used across all domains where user reviews exist: Places (business reviews), BushTrade (seller reviews), Novels (book reviews), Health (provider reviews), Nhimbe (event reviews). The reviewer verification tier is always visible — Ubuntu principle: trust is earned and displayed.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/rating\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-root-layout\",\"type\":\"registry:ui\",\"title\":\"mzizi Root Layout\",\"description\":\"Next.js App Router root layout. Composes all providers: ThemeProvider, SidebarProvider, Harness, LiveRegion portal, and font loading. The outermost wrapper of the entire application. Architecture doc: \\\"Root layout with MukokoThemeProvider\\\".\",\"categories\":[\"shell\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"mzizi-route-guard\",\"type\":\"registry:ui\",\"title\":\"mzizi Route Guard\",\"description\":\"Route-level protection gate. Checks auth state, role, subscription tier, and verification level before rendering a route. Redirects to appropriate screen (login, upgrade, verification) if requirements not met. Composes L4 safety gates at the route level.\",\"categories\":[\"shell\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"mzizi-route-planner\",\"type\":\"registry:ui\",\"title\":\"mzizi Route Planner\",\"description\":\"Branded route planning display composing route-card, stop-card, and itinerary-timeline primitives with gold mineral accent. Shows origin, destination, transit mode, duration, fare estimate, and step-by-step route. Integrates with Harare Metro data backbone and Nyuchi Logistics fleet.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/route-card\",\"https://api.mzizi.dev/v1/ui/stop-card\",\"https://api.mzizi.dev/v1/ui/itinerary-timeline\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-rsvp-button\",\"type\":\"registry:ui\",\"title\":\"mzizi Rsvp Button\",\"description\":\"Stateful RSVP button with confirmation status variants: pending, confirmed, waitlisted, declined. Pill-shaped with mineral accent, loading state, and capacity indicator. Maps to events.rsvp_action table. The standardized way to handle event registration across nhimbe.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-rum\",\"type\":\"registry:lib\",\"title\":\"mzizi Rum\",\"description\":\"Real User Monitoring. Collects page load timing, interaction delays, network quality, and navigation patterns from actual users. Aggregates by mini-app, page, device, and connection type. Privacy-respecting — no PII, only performance metrics. Competitors: Datadog RUM, Vercel Analytics.\",\"categories\":[\"observability\",\"assurance\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":8,\"nodeLabel\":\"assurance\",\"owner\":\"mzizi\",\"collection\":\"observability\"},{\"name\":\"mzizi-search-view\",\"type\":\"registry:ui\",\"title\":\"mzizi Search View\",\"description\":\"Universal branded search results view composing search-bar, filter-bar, and search-results primitives with mineral-accented category filters. Shows unified cross-app results: people, places, events, products, articles, groups. Each result type renders using its corresponding brand card (mzizi-listing-card, mzizi-place-card, user-card, mzizi-group-card). The standard search experience across the entire ecosystem.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/search-bar\",\"https://api.mzizi.dev/v1/ui/search-results\",\"https://api.mzizi.dev/v1/ui/filter-bar\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-section\",\"type\":\"registry:ui\",\"title\":\"mzizi Section\",\"description\":\"The workhorse section wrapper for every page. Combines error boundary, loading skeleton, health reporting, and optional lazy mounting into a single component. Every section of every page is wrapped in MziziSection. If one section fails, the rest keep working. Reports health status (healthy/degraded/error/loading) to the global health monitor. The architecture doc says: \\\"Every section of every page is wrapped in resilience.\\\"\",\"categories\":[\"resilience\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/utils\",\"https://api.mzizi.dev/v1/ui/error-boundary\",\"https://api.mzizi.dev/v1/ui/section-error-boundary\"],\"node\":5,\"nodeLabel\":\"resilience\",\"owner\":\"mzizi\",\"collection\":\"resilience\"},{\"name\":\"mzizi-seo\",\"type\":\"registry:lib\",\"title\":\"mzizi SEO\",\"description\":\"SEO metadata component for Next.js App Router. Generates head metadata, Open Graph tags, Twitter Cards, Schema.org JSON-LD structured data, and canonical URLs. Server component — runs at build/request time. Supports all Schema.org types used by the platform: JobPosting, VideoObject, MedicalWebPage, RentalCarReservation, Product, Event, Article, Recipe, etc.\",\"categories\":[\"pages\",\"discovery\"],\"dependencies\":[\"next\"],\"registryDependencies\":[],\"node\":11,\"nodeLabel\":\"discovery\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"mzizi-settings-page\",\"type\":\"registry:ui\",\"title\":\"mzizi Settings Page\",\"description\":\"Standard settings page orchestrator with sidebar navigation on desktop and accordion sections on mobile. Groups settings into titled sections with consistent row patterns (toggle, select, link, destructive). Every app and the global platform settings use this layout.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/mzizi-profile-settings\",\"https://api.mzizi.dev/v1/ui/switch\",\"https://api.mzizi.dev/v1/ui/select\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"mzizi-share-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Share Card\",\"description\":\"Universal branded share composition wrapping the share-dialog primitive. Shows a preview card of the shared content (title, image, source app), sharing options (copy link, share to Campfire, native share, social platforms), and a branded deep link. Every shareable piece of content across the ecosystem uses this pattern — listings, articles, profiles, places, events, videos, groups.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/sheet\",\"https://api.mzizi.dev/v1/ui/share-dialog\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-sidebar\",\"type\":\"registry:ui\",\"title\":\"mzizi Sidebar\",\"description\":\"Standardized sidebar for all Nyuchi ecosystem apps with mineral accent strip and section-based navigation.\",\"categories\":[\"shell\"],\"dependencies\":[\"next\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/sidebar\",\"https://api.mzizi.dev/v1/ui/separator\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/tooltip\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"mzizi-sidebar-nav\",\"type\":\"registry:ui\",\"title\":\"mzizi Sidebar Nav\",\"description\":\"Branded sidebar navigation with nav items, badges, and section headers. Extracted from admin-page, api-explorer-page, and logs-page. Uses mineral accents for active state.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-skeleton-set\",\"type\":\"registry:ui\",\"title\":\"mzizi Skeleton Set\",\"description\":\"DEPRECATED — Loading states are built into each component from Layer 2 up via the loading prop. Every component renders its own shaped skeleton matching its exact proportions. This top-down skeleton set is a legacy pattern. Use <MziziListingCard loading /> instead of <ListingSkeleton />. Will be removed in the next major version.\",\"categories\":[\"resilience\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/skeleton\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":5,\"nodeLabel\":\"resilience\",\"owner\":\"mzizi\",\"collection\":\"resilience\"},{\"name\":\"mzizi-source-badge\",\"type\":\"registry:ui\",\"title\":\"mzizi Source Badge\",\"description\":\"News source credibility indicator badge tied to verification tier. Shows the source name with a mineral-colored trust indicator. Maps to news.feed_source and news.news_media_organization verification status. Used inline on article cards and in source attribution lines.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-splash-screen\",\"type\":\"registry:ui\",\"title\":\"mzizi Splash Screen\",\"description\":\"Default splash/loading screen shown on app launch or during major transitions. Branded with mukoko wordmark and mineral accent. Supports progress indication for initial data hydration.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"mzizi-stats-row\",\"type\":\"registry:ui\",\"title\":\"mzizi Stats Row\",\"description\":\"Horizontal compact stats bar for dashboards, home screens, and admin views. Displays 2–4 metric blocks inline with icon, label, value, and optional trend indicator. Uses mineral-colored icon backgrounds. The brand-standard way to show key metrics at a glance across all Mukoko apps.\",\"categories\":[\"brand\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-success-screen\",\"type\":\"registry:ui\",\"title\":\"mzizi Success Screen\",\"description\":\"Branded success/confirmation screen with mineral-colored checkmark, title, message, and optional actions. Extracted from payment-page, checkout-page, and booking-page where the same checkmark + title pattern was repeated. Uses the Seven African Minerals for the success indicator.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-suitability-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Suitability Card\",\"description\":\"Branded suitability/recommendation card with score, icon, title, description, and mineral-colored score indicator. Universal pattern from mukoko-weather ActivityInsights. Used for: \\\"Good for farming\\\" (Weather), \\\"Good for families\\\" (Places), \\\"Good for outdoor exercise\\\" (Health), \\\"Safe to travel\\\" (Transport), \\\"Good match for you\\\" (Jobs). Any scored recommendation.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-sync-status\",\"type\":\"registry:ui\",\"title\":\"mzizi Sync Status\",\"description\":\"CouchDB sync state indicator. Shows sync progress, conflict count, and resolution status. The bridge between local-first data and cloud truth. Displays: synced, syncing (with progress), conflicts pending, sync error, and offline-queued states.\",\"categories\":[\"resilience\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":5,\"nodeLabel\":\"resilience\",\"owner\":\"mzizi\",\"collection\":\"resilience\"},{\"name\":\"mzizi-synthetic-probe\",\"type\":\"registry:lib\",\"title\":\"mzizi Synthetic Probe\",\"description\":\"Synthetic user journey probes that run on schedule. Defines step-by-step user flows (login → navigate → action → verify) and executes them against live or staging environments. Reports pass/fail with latency per step. Competitors: Datadog Synthetic Monitoring, Checkly.\",\"categories\":[\"observability\",\"assurance\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":8,\"nodeLabel\":\"assurance\",\"owner\":\"mzizi\",\"collection\":\"observability\"},{\"name\":\"mzizi-theme-provider\",\"type\":\"registry:ui\",\"title\":\"mzizi Theme Provider\",\"description\":\"Theme provider component that wraps the application and distributes design tokens via CSS custom properties and React context. Supports light/dark/high-contrast themes, per-brand accent overrides, system preference detection (prefers-color-scheme, prefers-contrast), and runtime theme switching. Every Nyuchi app wraps its root in this provider.\",\"categories\":[\"shell\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-tokens\",\"https://api.mzizi.dev/v1/ui/mzizi-tokens-typescript\"],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"mzizi-ticket-card\",\"type\":\"registry:ui\",\"title\":\"mzizi Ticket Card\",\"description\":\"Event ticket display card with QR code area, ticket tier, status indicator (valid/used/cancelled/transferred), and event details. Maps to events.ticket and events.ticket_tier tables. Used in My Events, check-in flows, and digital wallet views.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-timeline\",\"type\":\"registry:ui\",\"title\":\"mzizi Timeline\",\"description\":\"Universal date-railed discovery list — the 4.2.0 signature timeline. Groups items by day: each day is a left rail (weekday, big day numeral, month) beside a stack of tight horizontal rows. Each row reads time, title, host, location with an overlapping avatar stack and a right-edge thumbnail. Harness-wired for reduced-motion staggered entry and observability. Quiet rows, full 1px borders, 13px muted metadata — the 4.2.0 density. Used for event/agenda discovery across the ecosystem (nhimbe events, planner schedules, any date-anchored feed).\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-toast-provider\",\"type\":\"registry:ui\",\"title\":\"mzizi Toast Provider\",\"description\":\"App-level toast/notification stack. Manages positioning, stacking, auto-dismiss, and swipe-to-dismiss for transient notifications. Wraps the L2 toast primitive with app-level behavior.\",\"categories\":[\"shell\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"mzizi-tokens\",\"type\":\"registry:theme\",\"title\":\"mzizi Tokens\",\"description\":\"The Mzizi N1 design tokens as CSS custom properties — the Seven African Minerals and Seven Heritage colours with light/dark pairs, the four-level AAA surface system, radius scale, type scale, motion durations and touch targets. Generated from app/globals.css, so the registry and the site cannot disagree. Framework-agnostic by construction: the shadcn CLI merges these into whatever stylesheet the consuming project uses. Install this first — 431 of the registry's components reference these variables.\",\"categories\":[\"styling-libs\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-tokens-arkts\",\"type\":\"registry:lib\",\"title\":\"mzizi Tokens Arkts\",\"description\":\"Mzizi design tokens for ArkTS / HarmonyOS. Generated by `pnpm tokens:sync` from the same source that emits the web CSS custom properties — seven African Minerals + seven Heritage tones, both themes. Colour constants, spacing, radius and typography for HarmonyOS. Never hand-edited: adding a target means adding an emitter, never re-authoring the palette (CLAUDE.md §8.4.1).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-tokens-globals\",\"type\":\"registry:lib\",\"title\":\"mzizi Tokens Globals\",\"description\":\"The combined Mzizi `globals.css` — one self-contained stylesheet, generated by `pnpm tokens:sync` from `lib/tokens/palette.source.ts`. It carries `@import \\\"tailwindcss\\\"`, its own `@theme` block, `:root`, `.dark`, the per-brand blocks and every non-colour ladder, so a repo copies it in as its `globals.css` with nothing else to fetch — a copy cannot follow an `@import` to a sibling that was not copied with it. Set `data-brand` on `<html>` to pick a brand. Twenty-one colour families in three arrays of seven (minerals, heritage, experimental). Never hand-edited: adding a target means adding an emitter, never re-authoring the palette.\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-tokens-kotlin\",\"type\":\"registry:lib\",\"title\":\"mzizi Tokens Kotlin\",\"description\":\"Mzizi design tokens for Kotlin / Jetpack Compose. Generated by `pnpm tokens:sync` from the same source that emits the web CSS custom properties — seven African Minerals + seven Heritage tones, both themes. Color objects, spacing Dp values, radius and typography constants for Android. Never hand-edited: adding a target means adding an emitter, never re-authoring the palette (CLAUDE.md §8.4.1).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-tokens-python\",\"type\":\"registry:lib\",\"title\":\"mzizi Tokens Python\",\"description\":\"Mzizi design tokens for Python. Generated by `pnpm tokens:sync` from the same source that emits the web CSS custom properties — seven African Minerals + seven Heritage tones, both themes. Frozen dataclasses of colour values for analytics pipelines, data visualisation (matplotlib, plotly, altair), report generation and ML tooling. Never hand-edited: adding a target means adding an emitter, never re-authoring the palette (CLAUDE.md §8.4.1).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-tokens-react-native\",\"type\":\"registry:lib\",\"title\":\"mzizi Tokens React Native\",\"description\":\"Mzizi design tokens for React Native. Generated by `pnpm tokens:sync` from the same source that emits the web CSS custom properties — seven African Minerals + seven Heritage tones, both themes. A StyleSheet-ready token object for React Native. Never hand-edited: adding a target means adding an emitter, never re-authoring the palette (CLAUDE.md §8.4.1).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-tokens-rust\",\"type\":\"registry:lib\",\"title\":\"mzizi Tokens Rust\",\"description\":\"Mzizi design tokens for Rust. Generated by `pnpm tokens:sync` from the same source that emits the web CSS custom properties — seven African Minerals + seven Heritage tones, both themes, as `const` values plus `Palette`, `Spacing`, `Radius` and `Fonts`. This is what the Dioxus half of the registry consumes, via the `mzizi-tokens` crate. Never hand-edited: adding a target means adding an emitter, never re-authoring the palette (CLAUDE.md §8.4.1).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-tokens-swift\",\"type\":\"registry:lib\",\"title\":\"mzizi Tokens Swift\",\"description\":\"Mzizi design tokens for Swift / SwiftUI. Generated by `pnpm tokens:sync` from the same source that emits the web CSS custom properties — seven African Minerals + seven Heritage tones, both themes. Color extensions, spacing, radius and typography constants for iOS, macOS, watchOS and tvOS. Never hand-edited: adding a target means adding an emitter, never re-authoring the palette (CLAUDE.md §8.4.1).\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-tokens-typescript\",\"type\":\"registry:lib\",\"title\":\"mzizi Tokens Typescript\",\"description\":\"TypeScript constants mirroring the Mzizi N1 token set: typed primitive/semantic/component values plus the helpers (resolveColor, hexWithAlpha, generateCSSVariables) that components need at runtime. The canonical token DATA is mzizi-tokens, which ships the CSS custom properties themselves; this is the typed accessor for code that must read a token value rather than reference it in CSS.\",\"categories\":[\"styling-libs\",\"tokens\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-tokens\"],\"node\":1,\"nodeLabel\":\"tokens\",\"owner\":\"mzizi\",\"collection\":\"styling-libs\"},{\"name\":\"mzizi-transaction-row\",\"type\":\"registry:ui\",\"title\":\"mzizi Transaction Row\",\"description\":\"Transaction history row for the Mukoko wallet. Displays transaction type (send/receive/purchase/reward), amount with MIT currency formatting, counterparty, status, and timestamp. Maps to wallet.transaction table. Used in wallet history, payment confirmations, and financial reporting.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-trust-gate\",\"type\":\"registry:ui\",\"title\":\"mzizi Trust Gate\",\"description\":\"Trust score threshold gate. Blocks features that require a minimum trust score. Shows the user's current score, the required threshold, and actionable steps to improve. Maps to the Ubuntu Layer trust model: Status × Verification Tier → Trust Score. Used for: posting requires > 0.1, selling requires > 0.3, moderating requires > 0.5.\",\"categories\":[\"safety\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/utils\"],\"node\":4,\"nodeLabel\":\"safety\",\"owner\":\"mzizi\",\"collection\":\"safety\"},{\"name\":\"mzizi-trust-meter\",\"type\":\"registry:ui\",\"title\":\"mzizi Trust Meter\",\"description\":\"Full trust score visualization showing the three-signal breakdown: Verification Tier (are you real?), Platform Status (are you active?), and Ubuntu Contribution (are you helpful?). Displays the composite score as a meter bar with individual signal breakdowns. Used on profile detail pages, admin views, and trust audit screens. This is the expanded view of trust — the verified badge is the compact view.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/progress\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-update-prompt\",\"type\":\"registry:ui\",\"title\":\"mzizi Update Prompt\",\"description\":\"App update / new version available prompt. Shows when a new version is detected via service worker. Dismissible with optional force-update for critical patches.\",\"categories\":[\"shell\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":7,\"nodeLabel\":\"shell\",\"owner\":\"nyuchi\",\"collection\":\"shell\"},{\"name\":\"mzizi-user-card\",\"type\":\"registry:ui\",\"title\":\"mzizi User Card\",\"description\":\"Compact user profile card with avatar, name, role, and actions.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-user-menu\",\"type\":\"registry:ui\",\"title\":\"mzizi User Menu\",\"description\":\"A user profile dropdown menu with avatar, name, email, navigation links, and sign-out action.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/dropdown-menu\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-verified-badge\",\"type\":\"registry:ui\",\"title\":\"mzizi Verified Badge\",\"description\":\"Verification badge tied to the three-axis Mukoko trust model: Status (platform lifecycle) × Verification Tier (identity ladder) → Trust Score. Renders the appropriate mineral-colored badge for each tier — Terracotta (Community 0.10), Cobalt (OTP 0.20), Gold (Government 0.30), Tanzanite (Licensed 0.50). Suspended/ancestral accounts show a dimmed or memorial variant. Used inline next to names, on profile headers, listing cards, and anywhere trust needs to be communicated.\",\"categories\":[\"brand\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/tooltip\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"mzizi-wallet-gate\",\"type\":\"registry:ui\",\"title\":\"mzizi Wallet Gate\",\"description\":\"Web3 wallet connection gate. Verifies wallet is connected and optionally checks token balance (MIT soulbound, MXT transferable, NST storage, NHC utility). Shows branded connect CTA with mineral-colored wallet icon when not connected. Supports Cosmos SDK (Nyuchi Network), Polygon, and Ethereum wallets.\",\"categories\":[\"safety\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":4,\"nodeLabel\":\"safety\",\"owner\":\"mzizi\",\"collection\":\"safety\"},{\"name\":\"mzizi-washed-theme\",\"type\":\"registry:ui\",\"title\":\"mzizi Washed Theme\",\"description\":\"The one washed-theme mechanism — surface tinted by accent — that four independent implementations of the same idea collapse to. From a named palette family (the seven heritage tones or the seven minerals) or an explicit accent, it emits the full washed contract as inline custom properties every descendant inherits: --washed-accent, --wash, --washed-on-wash and --washed-gradient, plus the --event-primary / --event-on-wash / --event-gradient aliases existing call sites already read. Colours are var(--color-<family>, #canon) references, so the accent follows light/dark from the token layer for free and no hex is ever pasted bare; nothing is fetched at build or runtime. The wash is mixed surface-first in oklab, the same form and space as the repo's own --wash, with mix strengths read from --wash-surface / --wash-on-* so an app tunes dark mode in one rule. Exports resolveWashedTheme() for call sites that need the solved values rather than a wrapper element — a theme picker, an OG route, a stored event theme.\",\"categories\":[\"brand\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":3,\"nodeLabel\":\"brand\",\"owner\":\"nyuchi\",\"collection\":\"brand\"},{\"name\":\"native-select\",\"type\":\"registry:ui\",\"title\":\"Native Select\",\"description\":\"A native HTML select element styled to match the design system.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"navigation-menu\",\"type\":\"registry:ui\",\"title\":\"Navigation Menu\",\"description\":\"A collection of links for navigating websites.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"class-variance-authority\",\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"nearby-list\",\"type\":\"registry:ui\",\"title\":\"Nearby List\",\"description\":\"Proximity-sorted results list designed to pair with a map view. Shows distance, direction indicator, and listing preview. Standard split-view pattern for BushTrade browse, Places discovery, Nhimbe nearby events, and Transport nearby stops.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/map-view\",\"https://api.mzizi.dev/v1/ui/mzizi-listing-card\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"nft-card\",\"type\":\"registry:ui\",\"title\":\"Nft Card\",\"description\":\"NFT display card showing media (image/video), title, collection, owner, and marketplace actions. Used for Nhimbe ERC-1155 tickets, Novels chapter NFTs, Ubuntu badge NFTs, and creator content NFTs. Maps to wallet.nft_holdings.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"node-status-card\",\"type\":\"registry:ui\",\"title\":\"Node Status Card\",\"description\":\"Honeycomb node health and earnings display. Shows node type (consumer/business/infrastructure), uptime, storage contributed, NHC earned, and peer count. Used in the Honeycomb network dashboard.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/status-indicator\",\"https://api.mzizi.dev/v1/ui/progress\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"note-card\",\"type\":\"registry:ui\",\"title\":\"Note Card\",\"description\":\"Compact note display with title, excerpt, and timestamp.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"note-editor\",\"type\":\"registry:ui\",\"title\":\"Note Editor\",\"description\":\"Rich note editing interface with title, body (markdown or rich text), tags, and auto-save indicator. Supports inline images, checklists, and code blocks. The creation/editing counterpart to note-card (display). Reusable in any app that needs structured note capture.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/rich-text-editor\",\"https://api.mzizi.dev/v1/ui/tag-input\",\"https://api.mzizi.dev/v1/ui/input\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"notification-bell\",\"type\":\"registry:ui\",\"title\":\"Notification Bell\",\"description\":\"A notification bell icon with unread count badge and a popover panel listing notifications.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/popover\",\"https://api.mzizi.dev/v1/ui/scroll-area\",\"https://api.mzizi.dev/v1/ui/separator\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"notification-center\",\"type\":\"registry:block\",\"title\":\"Notification Center\",\"description\":\"Notification center with tabs for all, unread, and mentions with grouped items.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/scroll-area\",\"https://api.mzizi.dev/v1/ui/separator\",\"https://api.mzizi.dev/v1/ui/tabs\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"notification-center-full\",\"type\":\"registry:ui\",\"title\":\"Notification Center Full\",\"description\":\"Full-screen or drawer notification management surface. Extends notification-center (existing Layer 2 component) with filter/search, archive, bulk actions, and grouped conversations view. Audit flagged as missing UX pattern for mature notification UX. ALPHA — awaiting frontend implementation.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"notification-list\",\"type\":\"registry:ui\",\"title\":\"Notification List\",\"description\":\"Notification feed with read/unread states, timestamps, and date grouping.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"novel-cover\",\"type\":\"registry:ui\",\"title\":\"Novel Cover\",\"description\":\"Book/manga cover display with genre badge, rating, chapter count, and status indicator (ongoing/completed/hiatus). Aspect ratio optimized for book covers (2:3). Maps to novels.work table.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/rating\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"number-input\",\"type\":\"registry:ui\",\"title\":\"Number Input\",\"description\":\"Numeric input with increment and decrement buttons, min/max/step support.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"observability\",\"type\":\"registry:lib\",\"title\":\"Observability\",\"description\":\"Structured logging, performance measurement, and error tracking with the [mukoko] prefix.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"offline-banner\",\"type\":\"registry:ui\",\"title\":\"Offline Banner\",\"description\":\"Connection status indicator for the local-first architecture. Distinguishes between \\\"offline\\\" (no connection), \\\"showing cached data\\\" (offline but data available), and \\\"syncing\\\" (reconnecting). Persists at the top of the viewport. Dismissible but reappears on state change. Uses CouchDB sync status when available.\",\"categories\":[\"resilience\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\",\"https://api.mzizi.dev/v1/ui/utils\"],\"node\":5,\"nodeLabel\":\"resilience\",\"owner\":\"mzizi\",\"collection\":\"resilience\"},{\"name\":\"onboarding-flow\",\"type\":\"registry:block\",\"title\":\"Onboarding Flow\",\"description\":\"Multi-step onboarding flow with progress indicator, profile setup, and interest selection.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"onboarding-tour\",\"type\":\"registry:ui\",\"title\":\"Onboarding Tour\",\"description\":\"Step-by-step spotlight overlay guiding users through UI features.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"order-summary\",\"type\":\"registry:ui\",\"title\":\"Order Summary\",\"description\":\"Order total breakdown with line items and total.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"org-profile-page\",\"type\":\"registry:ui\",\"title\":\"Org Profile Page\",\"description\":\"Organization/business profile page. Logo, description, verification status, team members, location, hours, products/services tabs. For businesses, NGOs, government entities on the platform.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"page-header\",\"type\":\"registry:ui\",\"title\":\"Page Header\",\"description\":\"Standard page header with breadcrumbs, title, description, and action slot.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"pagination\",\"type\":\"registry:ui\",\"title\":\"Pagination\",\"description\":\"Pagination with page navigation, previous and next links.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"password-strength\",\"type\":\"registry:ui\",\"title\":\"Password Strength\",\"description\":\"Visual password strength meter with requirement checklist.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"payment-method-card\",\"type\":\"registry:ui\",\"title\":\"Payment Method Card\",\"description\":\"Payment method display with card brand and last four digits.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"payment-page\",\"type\":\"registry:ui\",\"title\":\"Payment Page\",\"description\":\"Payment page composition. Amount input, recipient selection, payment method, confirmation. Supports fiat (EcoCash, bank), crypto (MXT, NHC), and cross-border via Nhimbe. Competitors: M-Pesa, Revolut.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"payment-pin-pad\",\"type\":\"registry:ui\",\"title\":\"Payment Pin Pad\",\"description\":\"Secure numeric keypad for transaction PIN confirmation. Full-screen overlay with biometric fallback option. Mineral accent on confirm button. Never logs PIN values. Used before any financial transaction in Wallet.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/dialog\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"permission-badge\",\"type\":\"registry:ui\",\"title\":\"Permission Badge\",\"description\":\"Role/permission indicator badge with color coding.\",\"categories\":[\"primitives\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"phone-input\",\"type\":\"registry:ui\",\"title\":\"Phone Input\",\"description\":\"Phone number input with country code selector, African countries prioritized.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"pinned-message\",\"type\":\"registry:ui\",\"title\":\"Pinned Message\",\"description\":\"Pinned/starred message display banner in chat header area. Shows the pinned message content with unpin action and tap-to-scroll-to behavior. Used in Campfire conversations and Circles channels.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"pod-storage-meter\",\"type\":\"registry:ui\",\"title\":\"Pod Storage Meter\",\"description\":\"Personal sovereign pod storage usage visualization. Shows total allocation, used space by category (messages, media, documents, AI models), and upgrade path via NST. Data from Layer 1 (Pod) of the 7-layer data architecture.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/progress\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"poll\",\"type\":\"registry:ui\",\"title\":\"Poll\",\"description\":\"In-chat poll creation and voting interface. Supports single-choice and multi-choice with real-time vote counts and percentage bars. Used in Campfire group chats and Circles community channels. Mineral-colored progress bars.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/progress\",\"https://api.mzizi.dev/v1/ui/radio-group\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"popover\",\"type\":\"registry:ui\",\"title\":\"Popover\",\"description\":\"Displays rich content in a portal, triggered by a button.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"price-display\",\"type\":\"registry:ui\",\"title\":\"Price Display\",\"description\":\"Formatted price with currency, original price, and discount badge.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"pricing-card\",\"type\":\"registry:ui\",\"title\":\"Pricing Card\",\"description\":\"A pricing tier card with plan name, price, feature list, CTA button, and optional highlight badge.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/separator\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"priority-selector\",\"type\":\"registry:ui\",\"title\":\"Priority Selector\",\"description\":\"Task/item priority picker with visual indicators — none, low, medium, high, urgent. Mineral-colored dots: grey=none, cobalt=low, gold=medium, terracotta=high, destructive=urgent. Compact inline selector for use in task forms, issue trackers, and ticket systems.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/toggle-group\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"progress\",\"type\":\"registry:ui\",\"title\":\"Progress\",\"description\":\"Displays an indicator showing the completion progress of a task.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"prompt-input\",\"type\":\"registry:ui\",\"title\":\"Prompt Input\",\"description\":\"Expandable AI prompt input with send/stop buttons and streaming support.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"property-list\",\"type\":\"registry:ui\",\"title\":\"Property List\",\"description\":\"Key-value pair display with labels, values, and optional copy actions.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"pull-to-refresh\",\"type\":\"registry:ui\",\"title\":\"Pull To Refresh\",\"description\":\"Mobile pull-down-to-refresh gesture handler.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"quick-action-grid\",\"type\":\"registry:ui\",\"title\":\"Quick Action Grid\",\"description\":\"Grid of quick action buttons with icon and label. Extracted from wallet-page and console-dashboard-page.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"radio-group\",\"type\":\"registry:ui\",\"title\":\"Radio Group\",\"description\":\"A set of checkable buttons where only one can be checked at a time.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"rate-limiter\",\"type\":\"registry:lib\",\"title\":\"Rate Limiter\",\"description\":\"Token bucket rate limiter inspired by Resilience4j RateLimiter. Controls request frequency to protect API quotas (Tomorrow.io, Anthropic, government registries) and prevent thundering herd problems. Supports configurable limits per time window, burst allowance, and queue-based waiting. Integrates with observability for tracking request rates, rejections, and quota utilization. Critical for the Nyuchi 7-layer architecture where multiple data sources have different rate limits.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/observability\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"rating\",\"type\":\"registry:ui\",\"title\":\"Rating\",\"description\":\"An interactive star rating component with hover preview, read-only mode, and mineral gold accent.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"reaction-picker\",\"type\":\"registry:ui\",\"title\":\"Reaction Picker\",\"description\":\"Emoji reaction selector popover with counts.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"read-receipts\",\"type\":\"registry:ui\",\"title\":\"Read Receipts\",\"description\":\"Seen-by indicator with avatar stack showing who has read a message. Expandable to full list. Shows read timestamps. Maps to campfire.message read status. The WhatsApp blue-tick pattern.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar-group\",\"https://api.mzizi.dev/v1/ui/tooltip\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"reading-progress\",\"type\":\"registry:ui\",\"title\":\"Reading Progress\",\"description\":\"Book/series progress bar with chapter markers. Shows current position in the overall work and estimated time remaining. Mineral-colored progress fill. Used at the top of the reader and on novel cards.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/progress\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"recording-controls\",\"type\":\"registry:ui\",\"title\":\"Recording Controls\",\"description\":\"Record/pause/stop button group with timer display and recording state indicators. The standard media capture control bar. Used in Bytes (video), Campfire (voice notes), and any creation flow involving recording.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button-group\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"recurrence-picker\",\"type\":\"registry:ui\",\"title\":\"Recurrence Picker\",\"description\":\"Recurrence pattern selector for scheduling — daily, weekly, monthly, yearly with day-of-week/month selectors and end-date or occurrence-count options. Used for recurring tasks, events, medication schedules, and any repeating item across the platform.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/select\",\"https://api.mzizi.dev/v1/ui/checkbox\",\"https://api.mzizi.dev/v1/ui/date-picker\",\"https://api.mzizi.dev/v1/ui/number-input\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"reminder-card\",\"type\":\"registry:ui\",\"title\":\"Reminder Card\",\"description\":\"Notification-style reminder card with title, due datetime, snooze/dismiss actions, and recurrence indicator. Used for task deadlines, appointment reminders, medication timing, and any time-triggered alert across the platform.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"report-page\",\"type\":\"registry:ui\",\"title\":\"Report Page\",\"description\":\"Report/document page. Structured report with title, date, sections, charts, tables, and print/export actions. For generated analytics reports, compliance docs, audit trails.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"resizable\",\"type\":\"registry:ui\",\"title\":\"Resizable\",\"description\":\"Accessible resizable panel groups and layouts with keyboard support.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"react-resizable-panels\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"retry\",\"type\":\"registry:lib\",\"title\":\"Retry\",\"description\":\"Retry with exponential backoff, jitter, and configurable retry predicates.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/observability\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"revenue-dashboard-widget\",\"type\":\"registry:ui\",\"title\":\"Revenue Dashboard Widget\",\"description\":\"Creator and platform revenue metrics with earnings chart, payout history, next payout date, and revenue split visualization. Used in creator dashboards (Novels, Bytes) and Nyuchi Console financial overview.\",\"categories\":[\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"review-card\",\"type\":\"registry:ui\",\"title\":\"Review Card\",\"description\":\"Review or testimonial display with star rating, author info, and review text.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"rich-text-editor\",\"type\":\"registry:ui\",\"title\":\"Rich Text Editor\",\"description\":\"Simple WYSIWYG editor with toolbar for bold, italic, heading, list, and link formatting.\",\"categories\":[\"primitives\"],\"dependencies\":[\"dompurify\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"role-selector\",\"type\":\"registry:ui\",\"title\":\"Role Selector\",\"description\":\"Role picker with descriptions and permission previews.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"route-card\",\"type\":\"registry:ui\",\"title\":\"Route Card\",\"description\":\"Transit route display showing stops, estimated time, fare, and transport mode (bus, kombi, train). Maps to transport.transit_route. Used in Transport mini-app route search results.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/separator\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"rtl-conformity-check\",\"type\":\"registry:ui\",\"title\":\"Rtl Conformity Check\",\"description\":\"N8 assurance probe that validates RTL (right-to-left) layout correctness across deployed components. Checks: logical properties (start/end vs left/right), bidi text rendering, icon mirroring rules, text alignment consistency. Audit flagged as missing accessibility infrastructure. ALPHA — awaiting edge function implementation.\",\"categories\":[\"observability\",\"assurance\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":8,\"nodeLabel\":\"assurance\",\"owner\":\"mzizi\",\"collection\":\"observability\"},{\"name\":\"schema-viewer\",\"type\":\"registry:ui\",\"title\":\"Schema Viewer\",\"description\":\"JSON schema tree display with type badges.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"scroll-area\",\"type\":\"registry:ui\",\"title\":\"Scroll Area\",\"description\":\"Augments native scroll functionality for custom, cross-browser styling.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"search-bar\",\"type\":\"registry:ui\",\"title\":\"Search Bar\",\"description\":\"A search input with keyboard shortcut support, clear button, and command palette integration.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/kbd\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"search-results\",\"type\":\"registry:block\",\"title\":\"Search Results\",\"description\":\"Standard search results page with filters, result items, and pagination.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/separator\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"section-error-boundary\",\"type\":\"registry:ui\",\"title\":\"Section Error Boundary\",\"description\":\"Section-level error boundary for the 5-layer architecture with retry, named sections, and observability logging.\",\"categories\":[\"resilience\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/observability\",\"https://api.mzizi.dev/v1/ui/utils\",\"https://api.mzizi.dev/v1/ui/button\"],\"node\":5,\"nodeLabel\":\"resilience\",\"owner\":\"mzizi\",\"collection\":\"resilience\"},{\"name\":\"section-header\",\"type\":\"registry:ui\",\"title\":\"Section Header\",\"description\":\"Section divider with title, description, action, and mineral accent.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"segmented-control\",\"type\":\"registry:ui\",\"title\":\"Segmented Control\",\"description\":\"Connected multi-option selector as a single pill-shaped control. Inline alternative to radio-group for 2-4 mutually exclusive options like view mode switchers, time range selectors, or binary choices. Audit flagged as missing primitive. ALPHA — awaiting frontend implementation.\",\"categories\":[\"primitives\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"select\",\"type\":\"registry:ui\",\"title\":\"Select\",\"description\":\"Displays a list of options for the user to pick from, triggered by a button.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"send-money-flow\",\"type\":\"registry:ui\",\"title\":\"Send Money Flow\",\"description\":\"Multi-step money transfer flow: recipient selection → amount entry → confirmation → receipt. Supports QR scan, contact picker, and recent recipients. The standard peer-to-peer transfer pattern for Wallet and Campfire bill splitting.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/currency-input\",\"https://api.mzizi.dev/v1/ui/stepper\",\"https://api.mzizi.dev/v1/ui/transaction-receipt\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"separator\",\"type\":\"registry:ui\",\"title\":\"Separator\",\"description\":\"Visually or semantically separates content.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"service-health-card\",\"type\":\"registry:ui\",\"title\":\"Service Health Card\",\"description\":\"Service health status card with name, status dot, uptime, and latency. Extracted from infrastructure-page.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"session-list\",\"type\":\"registry:ui\",\"title\":\"Session List\",\"description\":\"Active session display with device, location, and revoke.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"settings-layout\",\"type\":\"registry:ui\",\"title\":\"Settings Layout\",\"description\":\"Two-column settings page layout with sticky sidebar navigation.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"severity-badge\",\"type\":\"registry:ui\",\"title\":\"Severity Badge\",\"description\":\"Color-coded severity indicator with mineral-mapped colors. Levels: low (malachite), moderate (gold), high (terracotta/orange), severe (red), extreme (dark red), cold (cobalt). Used for weather alerts, health risk levels, trust warnings, content moderation flags, and any graduated severity display. Production-proven in mukoko-weather FrostAlertBanner.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/utils\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"share-dialog\",\"type\":\"registry:ui\",\"title\":\"Share Dialog\",\"description\":\"A share dialog with copy-to-clipboard, social media links, and native share support.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/dialog\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/separator\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"sheet\",\"type\":\"registry:ui\",\"title\":\"Sheet\",\"description\":\"Extends the Dialog component to display content that complements the main content of the screen.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar\",\"type\":\"registry:ui\",\"title\":\"Sidebar\",\"description\":\"A composable, themeable and customizable sidebar component.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"class-variance-authority\",\"radix-ui\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/separator\",\"https://api.mzizi.dev/v1/ui/sheet\",\"https://api.mzizi.dev/v1/ui/skeleton\",\"https://api.mzizi.dev/v1/ui/tooltip\",\"https://api.mzizi.dev/v1/ui/use-mobile\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-01\",\"type\":\"registry:block\",\"title\":\"Sidebar 01\",\"description\":\"Basic sidebar with icon and text navigation items.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-02\",\"type\":\"registry:block\",\"title\":\"Sidebar 02\",\"description\":\"Collapsible sidebar with grouped sections.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-03\",\"type\":\"registry:block\",\"title\":\"Sidebar 03\",\"description\":\"Sidebar with search bar at top.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-04\",\"type\":\"registry:block\",\"title\":\"Sidebar 04\",\"description\":\"Sidebar with user profile section.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-05\",\"type\":\"registry:block\",\"title\":\"Sidebar 05\",\"description\":\"Sidebar with nested sub-navigation.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-06\",\"type\":\"registry:block\",\"title\":\"Sidebar 06\",\"description\":\"Icon-only collapsed sidebar.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-07\",\"type\":\"registry:block\",\"title\":\"Sidebar 07\",\"description\":\"Sidebar with badge notification counts.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-08\",\"type\":\"registry:block\",\"title\":\"Sidebar 08\",\"description\":\"Sidebar with header and footer sections.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-09\",\"type\":\"registry:block\",\"title\":\"Sidebar 09\",\"description\":\"Sidebar with grouped navigation categories.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-10\",\"type\":\"registry:block\",\"title\":\"Sidebar 10\",\"description\":\"Floating sidebar detached from edge.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-11\",\"type\":\"registry:block\",\"title\":\"Sidebar 11\",\"description\":\"Sidebar with accordion sections.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-12\",\"type\":\"registry:block\",\"title\":\"Sidebar 12\",\"description\":\"Sidebar with breadcrumb trail.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-13\",\"type\":\"registry:block\",\"title\":\"Sidebar 13\",\"description\":\"Sidebar with quick actions panel.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-14\",\"type\":\"registry:block\",\"title\":\"Sidebar 14\",\"description\":\"Sidebar with theme toggle.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-15\",\"type\":\"registry:block\",\"title\":\"Sidebar 15\",\"description\":\"Sidebar with workspace switcher.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"sidebar-16\",\"type\":\"registry:block\",\"title\":\"Sidebar 16\",\"description\":\"Full-featured sidebar combining all patterns.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"signup-01\",\"type\":\"registry:block\",\"title\":\"Signup 01\",\"description\":\"Basic signup with name, email, and password fields.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"signup-02\",\"type\":\"registry:block\",\"title\":\"Signup 02\",\"description\":\"Multi-step signup wizard with progress indicator.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"signup-03\",\"type\":\"registry:block\",\"title\":\"Signup 03\",\"description\":\"Split signup with features showcase panel.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"signup-04\",\"type\":\"registry:block\",\"title\":\"Signup 04\",\"description\":\"Invite-only signup with invite code input.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"signup-05\",\"type\":\"registry:block\",\"title\":\"Signup 05\",\"description\":\"Social and email signup with terms checkbox.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/input\",\"https://api.mzizi.dev/v1/ui/label\",\"https://api.mzizi.dev/v1/ui/mzizi-harness\"],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"skeleton\",\"type\":\"registry:ui\",\"title\":\"Skeleton\",\"description\":\"Used to show a placeholder while content is loading.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"slider\",\"type\":\"registry:ui\",\"title\":\"Slider\",\"description\":\"An input where the user selects a value from within a given range.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"social-feed-page\",\"type\":\"registry:ui\",\"title\":\"Social Feed Page\",\"description\":\"Social feed page. Infinite scroll content feed with stories/status bar, create post FAB, pull-to-refresh. Competitors: Instagram, TikTok, Twitter.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"sonner\",\"type\":\"registry:ui\",\"title\":\"Sonner\",\"description\":\"An opinionated toast component for React.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"sonner\",\"next-themes\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"source-citation\",\"type\":\"registry:ui\",\"title\":\"Source Citation\",\"description\":\"Citation card linking AI responses to source documents.\",\"categories\":[\"primitives\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"spinner\",\"type\":\"registry:ui\",\"title\":\"Spinner\",\"description\":\"A loading spinner component.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"split-bill\",\"type\":\"registry:ui\",\"title\":\"Split Bill\",\"description\":\"Bill splitting interface for group payments in Campfire. Supports equal split, custom amounts per person, and tip. Shows each participant with their share and payment status. Maps to wallet.payment_intents.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/currency-input\",\"https://api.mzizi.dev/v1/ui/avatar-group\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"split-view\",\"type\":\"registry:ui\",\"title\":\"Split View\",\"description\":\"Responsive master-detail layout that collapses on mobile.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"stats-card\",\"type\":\"registry:ui\",\"title\":\"Stats Card\",\"description\":\"A metric card displaying a value with optional trend indicator, icon, and description.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"status-dot\",\"type\":\"registry:ui\",\"title\":\"Status Dot\",\"description\":\"Colored status dot with optional label. Uses semantic status tokens. Extracted from console-dashboard-page, infrastructure-page, and logs-page where colored dots indicated service health inline.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"status-indicator\",\"type\":\"registry:ui\",\"title\":\"Status Indicator\",\"description\":\"A status dot with optional label indicating online, offline, busy, away, or live states.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"step-progress\",\"type\":\"registry:ui\",\"title\":\"Step Progress\",\"description\":\"Horizontal step progress indicator for multi-step flows. Shows current progress as filled/unfilled segments. Extracted from payment-page, checkout-page, booking-page, and verification-page. Brand-agnostic.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"stepper\",\"type\":\"registry:ui\",\"title\":\"Stepper\",\"description\":\"Multi-step wizard navigation with horizontal/vertical orientation variants.\",\"categories\":[\"primitives\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"sticky-bar\",\"type\":\"registry:ui\",\"title\":\"Sticky Bar\",\"description\":\"Position-sticky container for headers or action bars.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"stop-card\",\"type\":\"registry:ui\",\"title\":\"Stop Card\",\"description\":\"Transit stop card showing stop name, arriving vehicles with ETAs, and distance from user. Maps to transport.transit_stop (bus stops, train stations, kombi ranks, taxi ranks). Used in Transport stop detail and nearby-stops views.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"story-creator\",\"type\":\"registry:ui\",\"title\":\"Story Creator\",\"description\":\"Ephemeral content creation interface combining camera viewfinder, text overlay, sticker placement, and drawing tools. Used for story/status content that expires after 24 hours. Maps to the Pulse content feed.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/camera-viewfinder\",\"https://api.mzizi.dev/v1/ui/caption-editor\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"streaming-text\",\"type\":\"registry:ui\",\"title\":\"Streaming Text\",\"description\":\"Typewriter effect text renderer for streaming LLM output.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"subscription-card\",\"type\":\"registry:ui\",\"title\":\"Subscription Card\",\"description\":\"Plan comparison card with features, price, and CTA.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"subscription-gate\",\"type\":\"registry:ui\",\"title\":\"Subscription Gate\",\"description\":\"Subscribe-to-read paywall with tier preview, price display, and CTA. Shows a blurred preview of gated content with clear upgrade path. Used in Novels for chapter unlocks and premium content across the platform.\",\"categories\":[\"safety\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/badge\"],\"node\":4,\"nodeLabel\":\"safety\",\"owner\":\"mzizi\",\"collection\":\"safety\"},{\"name\":\"suggested-prompts\",\"type\":\"registry:ui\",\"title\":\"Suggested Prompts\",\"description\":\"Scrollable prompt chips for AI conversations.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"switch\",\"type\":\"registry:ui\",\"title\":\"Switch\",\"description\":\"A control that allows the user to toggle between a checked and not checked state.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"symptom-checker\",\"type\":\"registry:ui\",\"title\":\"Symptom Checker\",\"description\":\"Guided symptom input with body region selector, severity scale, and duration fields. Not a diagnostic tool — produces a structured symptom summary for telemedicine consultations. Used in Health mini-app pre-appointment flow.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/checkbox\",\"https://api.mzizi.dev/v1/ui/slider\",\"https://api.mzizi.dev/v1/ui/stepper\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"table\",\"type\":\"registry:ui\",\"title\":\"Table\",\"description\":\"A responsive table component.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"tabs\",\"type\":\"registry:ui\",\"title\":\"Tabs\",\"description\":\"A set of layered sections of content that are displayed one at a time.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"class-variance-authority\",\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"tag-input\",\"type\":\"registry:ui\",\"title\":\"Tag Input\",\"description\":\"Token-based input for multiple values as dismissible badges.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"task-board\",\"type\":\"registry:ui\",\"title\":\"Task Board\",\"description\":\"Draggable task card for use in kanban boards, task lists, and project management views. Shows title, priority indicator, due date, assignee avatar, labels, and subtask progress. Composable into kanban-board columns, list views, or calendar day slots. A reusable building block for any task-driven interface.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/checkbox\",\"https://api.mzizi.dev/v1/ui/progress\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"team-management-page\",\"type\":\"registry:ui\",\"title\":\"Team Management Page\",\"description\":\"Team/organization management page. Member list, role assignment, invitations, permissions, activity log. For Mukoko orgs, Nyuchi Web Services teams, B2B partners.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"telemedicine-widget\",\"type\":\"registry:ui\",\"title\":\"Telemedicine Widget\",\"description\":\"Video call interface card for virtual consultations. Shows call status, timer, mute/camera toggle, and end call button. Integrates with the Health mini-app and Nyuchi Medical for scheduled and on-demand consultations.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/avatar\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"text-size-adjuster\",\"type\":\"registry:ui\",\"title\":\"Text Size Adjuster\",\"description\":\"Dynamic text sizing control for readability. Supports preset sizes (small/default/large/extra-large) and custom scaling. Persisted in user preferences. Critical for accessibility across diverse user needs.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/slider\",\"https://api.mzizi.dev/v1/ui/button-group\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"textarea\",\"type\":\"registry:ui\",\"title\":\"Textarea\",\"description\":\"Displays a form textarea.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"time-picker\",\"type\":\"registry:ui\",\"title\":\"Time Picker\",\"description\":\"Hour and minute selector with optional AM/PM toggle.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"time-series-chart\",\"type\":\"registry:ui\",\"title\":\"Time Series Chart\",\"description\":\"Reusable Canvas-based time series chart with multi-series support, dual Y-axes, mixed line/bar types, custom tooltip and tick formatters. Built on canvas-chart. Draws lines, areas, and bars on a single canvas element. Production-proven in mukoko-weather for temperature trends, precipitation, humidity, UV index, and wind speed charts. Use this instead of composing individual Recharts chart blocks when you need a data-driven chart with dynamic series.\",\"categories\":[\"primitives\"],\"dependencies\":[\"chart.js\",\"react-chartjs-2\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/canvas-chart\",\"https://api.mzizi.dev/v1/ui/mzizi-tokens\",\"https://api.mzizi.dev/v1/ui/mzizi-tokens-typescript\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"time-slot-picker\",\"type\":\"registry:ui\",\"title\":\"Time Slot Picker\",\"description\":\"Selectable grid of available time slots for booking.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"time-tracker\",\"type\":\"registry:ui\",\"title\":\"Time Tracker\",\"description\":\"Elapsed time display with start/stop/pause controls. Shows running timer, accumulated time, and optional description. Used for time tracking on tasks, billable hours in Nyuchi Tools, and focus sessions.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button-group\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"timeline\",\"type\":\"registry:ui\",\"title\":\"Timeline\",\"description\":\"A vertical timeline for displaying chronological events with mineral-colored dots.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"timeout\",\"type\":\"registry:lib\",\"title\":\"Timeout\",\"description\":\"Promise timeout wrapper. Rejects with TimeoutError if an async operation exceeds the specified duration.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"toast\",\"type\":\"registry:ui\",\"title\":\"Toast\",\"description\":\"A succinct message that is displayed temporarily.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"@radix-ui/react-toast\",\"class-variance-authority\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"toaster\",\"type\":\"registry:ui\",\"title\":\"Toaster\",\"description\":\"A toaster component to render toast notifications.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/toast\",\"https://api.mzizi.dev/v1/ui/use-toast\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"todo-item\",\"type\":\"registry:ui\",\"title\":\"Todo Item\",\"description\":\"Checkable task item with priority, due date, and assignee.\",\"categories\":[\"primitives\"],\"dependencies\":[\"class-variance-authority\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"toggle\",\"type\":\"registry:ui\",\"title\":\"Toggle\",\"description\":\"A two-state button that can be either on or off.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"class-variance-authority\",\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"toggle-group\",\"type\":\"registry:ui\",\"title\":\"Toggle Group\",\"description\":\"A set of two-state buttons that can be toggled on or off.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"class-variance-authority\",\"radix-ui\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/toggle\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"token-balance-card\",\"type\":\"registry:ui\",\"title\":\"Token Balance Card\",\"description\":\"Multi-token balance overview showing all four Mukoko tokens (MIT, MXT, NST, NHC) with individual balances, fiat equivalents, and combined portfolio value. Maps to wallet.balance and wallet.mit_token tables.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/separator\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"token-row\",\"type\":\"registry:ui\",\"title\":\"Token Row\",\"description\":\"Crypto token display row with symbol, balance, and optional fiat value. Extracted from wallet-page.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"toolbar\",\"type\":\"registry:ui\",\"title\":\"Toolbar\",\"description\":\"Horizontal container for grouped tool actions. Persistent action bar pattern for editors, content creation, and admin surfaces. Supports segmented groups, overflow menu, and responsive collapse. Audit flagged as missing UX pattern. ALPHA — awaiting frontend implementation.\",\"categories\":[\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"tooltip\",\"type\":\"registry:ui\",\"title\":\"Tooltip\",\"description\":\"A popup that displays information related to an element when it receives keyboard focus or the mouse hovers over it.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"radix-ui\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"transaction-history-page\",\"type\":\"registry:ui\",\"title\":\"Transaction History Page\",\"description\":\"Transaction history page. Filterable list of all transactions with date grouping, search, and export. Shows transaction type, status, amount, and counterparty.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"transaction-receipt\",\"type\":\"registry:ui\",\"title\":\"Transaction Receipt\",\"description\":\"Full payment receipt with QR code, transaction ID, amount, parties, timestamp, and shareable format. Maps to wallet.transaction table. Used after successful payments, transfers, and purchases across Wallet, BushTrade, and Nhimbe.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/separator\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"transfer-list\",\"type\":\"registry:ui\",\"title\":\"Transfer List\",\"description\":\"Dual-list selector with search filtering and arrow buttons to move items between panels.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/input\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"translation-indicator\",\"type\":\"registry:ui\",\"title\":\"Translation Indicator\",\"description\":\"Shows translation status and language of content. Indicates whether content is original, machine-translated, or community-translated. Links to Lingo for translation contribution. Used on articles, listings, and chat messages.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/tooltip\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"tree-view\",\"type\":\"registry:ui\",\"title\":\"Tree View\",\"description\":\"Hierarchical expandable/collapsible node list for nested data.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"typing-indicator\",\"type\":\"registry:ui\",\"title\":\"Typing Indicator\",\"description\":\"Animated dots showing someone is typing.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"typography\",\"type\":\"registry:ui\",\"title\":\"Typography\",\"description\":\"A set of typographic components for consistent text styling.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"use-memory-pressure\",\"type\":\"registry:hook\",\"title\":\"Use Memory Pressure\",\"description\":\"Monitor JS heap memory pressure via Performance Memory API.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"use-mobile\",\"type\":\"registry:hook\",\"title\":\"Use Mobile\",\"description\":\"A hook to detect if the current device is mobile.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"use-toast\",\"type\":\"registry:hook\",\"title\":\"Use Toast\",\"description\":\"A hook for managing toast notifications.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/toast\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"user-management-row\",\"type\":\"registry:ui\",\"title\":\"User Management Row\",\"description\":\"Admin user management table row with avatar, name, email, role badge, verification tier, last active timestamp, and action dropdown (edit role, suspend, delete). Used in all Nyuchi product admin panels.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/avatar\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/dropdown-menu\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"utils\",\"type\":\"registry:lib\",\"title\":\"Utils\",\"description\":\"Utility functions including the cn classname helper.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[\"clsx\",\"tailwind-merge\"],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"vehicle-card\",\"type\":\"registry:ui\",\"title\":\"Vehicle Card\",\"description\":\"Vehicle listing card with photo, specs, availability calendar, driver rating, and booking CTA. Maps to Nyuchi Logistics fleet data. Used in Transport vehicle booking and BushTrade delivery options.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/rating\",\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"verification-page\",\"type\":\"registry:ui\",\"title\":\"Verification Page\",\"description\":\"Identity verification page. Multi-step verification flow: phone/OTP, government ID upload, selfie, biometric. Shows current tier and what unlocks at each level.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"verification-review-card\",\"type\":\"registry:ui\",\"title\":\"Verification Review Card\",\"description\":\"Identity/business verification submission card with submitted evidence preview, tier requested, applicant info, and approve/reject/request-more actions. Maps to system.unified_submissions and system.verification_tier.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/badge\",\"https://api.mzizi.dev/v1/ui/button-group\",\"https://api.mzizi.dev/v1/ui/mzizi-verified-badge\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"video-player\",\"type\":\"registry:ui\",\"title\":\"Video Player\",\"description\":\"Themed video player wrapper with custom controls.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"video-trimmer\",\"type\":\"registry:ui\",\"title\":\"Video Trimmer\",\"description\":\"Timeline scrubber for video editing with frame preview thumbnails, start/end handles, and playhead. Supports pinch-to-zoom on mobile for precise trimming. Used in Bytes post-capture editing flow.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/slider\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"virtual-list\",\"type\":\"registry:ui\",\"title\":\"Virtual List\",\"description\":\"Virtualized list for large datasets using CSS transforms.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"vitals-display\",\"type\":\"registry:ui\",\"title\":\"Vitals Display\",\"description\":\"Health metrics visualization showing blood pressure, heart rate, BMI, temperature, and custom vitals. Chart-based trend view with mineral-colored ranges (normal=malachite, warning=gold, critical=terracotta). Data from sovereign pod.\",\"categories\":[\"primitives\"],\"dependencies\":[\"recharts\"],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/chart\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"voice-note-player\",\"type\":\"registry:ui\",\"title\":\"Voice Note Player\",\"description\":\"Inline voice message player with waveform visualization, play/pause, speed control (1x/1.5x/2x), and duration. The WhatsApp-standard voice message pattern adapted for the Nyuchi ecosystem with mineral accent on the waveform.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/audio-waveform\",\"https://api.mzizi.dev/v1/ui/button\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"vote-buttons\",\"type\":\"registry:ui\",\"title\":\"Vote Buttons\",\"description\":\"For/against/abstain voting interface with visual weight display. Shows current vote distribution and the user's selected option. Mineral colors: malachite=for, terracotta=against, gold=abstain. Used in DAO governance proposals.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button-group\",\"https://api.mzizi.dev/v1/ui/progress\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"wallet-card\",\"type\":\"registry:ui\",\"title\":\"Wallet Card\",\"description\":\"Visual card representation for digital wallet display. Shows token type (MIT/MXT/NST/NHC), balance, and card design with mineral gradient. Swipeable between multiple token cards. Maps to wallet.balance table.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/card\",\"https://api.mzizi.dev/v1/ui/carousel\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"},{\"name\":\"wallet-connect-button\",\"type\":\"registry:ui\",\"title\":\"Wallet Connect Button\",\"description\":\"Connect/disconnect wallet button with truncated address display and network indicator. Supports Nyuchi custodial wallet (default) and self-custody upgrade path. Used across all Web3-enabled features.\",\"categories\":[\"components\",\"primitives\"],\"dependencies\":[],\"registryDependencies\":[\"https://api.mzizi.dev/v1/ui/button\",\"https://api.mzizi.dev/v1/ui/dropdown-menu\"],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"framework\",\"collection\":\"components\"},{\"name\":\"wallet-page\",\"type\":\"registry:ui\",\"title\":\"Wallet Page\",\"description\":\"Wallet page composition. Balance display, quick actions (send/receive/scan), recent transactions, and token portfolio. Competitors: M-Pesa, Revolut, Cash App. Supports MXT, MIT, NST, NHC tokens alongside fiat currencies.\",\"categories\":[\"pages\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\"},{\"name\":\"webhook-card\",\"type\":\"registry:ui\",\"title\":\"Webhook Card\",\"description\":\"Webhook endpoint configuration display with status.\",\"categories\":[\"primitives\"],\"dependencies\":[],\"registryDependencies\":[],\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\"}],\"meta\":{\"total\":577,\"count\":577,\"offset\":0,\"limit\":null,\"registryTotal\":577,\"filters\":{\"node\":null,\"owner\":null,\"collection\":null,\"type\":null}}}",
|
|
@@ -1180,6 +1180,72 @@ export const COMPONENT_DOCS = {
|
|
|
1180
1180
|
"wallet-page": "{\"name\":\"wallet-page\",\"description\":\"Wallet page composition. Balance display, quick actions (send/receive/scan), recent transactions, and token portfolio. Competitors: M-Pesa, Revolut, Cash App. Supports MXT, MIT, NST, NHC tokens alongside fiat currencies.\",\"node\":6,\"nodeLabel\":\"pages\",\"owner\":\"nyuchi\",\"collection\":\"pages\",\"docs\":{\"id\":0,\"component_name\":\"wallet-page\",\"use_cases\":[\"nhimbe\",\"wallet\",\"bushtrade\"],\"variants\":[\"default\",\"loading\"],\"sizes\":[],\"features\":[\"X-axis page composition\",\"Pure composition — no inline UI\",\"Semantic CSS vars only\",\"Children/slots for content\"],\"a11y\":[\"role=main\",\"aria-label\",\"Loading state with skeleton\",\"Focus management\"],\"examples\":[],\"created_at\":\"\",\"updated_at\":\"\"},\"demo\":{\"id\":0,\"component_name\":\"wallet-page\",\"has_demo\":true,\"demo_type\":null,\"created_at\":\"\",\"updated_at\":\"\"}}",
|
|
1181
1181
|
"webhook-card": "{\"name\":\"webhook-card\",\"description\":\"Webhook endpoint configuration display with status.\",\"node\":2,\"nodeLabel\":\"primitives\",\"owner\":\"mzizi\",\"collection\":\"primitives\",\"docs\":{\"id\":0,\"component_name\":\"webhook-card\",\"use_cases\":[\"Webhook endpoint configuration in integrations\",\"Event subscription management\",\"Webhook delivery status monitoring\",\"Third-party integration setup\",\"Event-driven workflow configuration\"],\"variants\":[\"default\",\"sm\",\"lg\"],\"sizes\":[],\"features\":[\"cn() for className composition\",\"data-slot attribute\"],\"a11y\":[\"Semantic HTML\"],\"examples\":[],\"created_at\":\"\",\"updated_at\":\"\"},\"demo\":{\"id\":0,\"component_name\":\"webhook-card\",\"has_demo\":true,\"demo_type\":null,\"created_at\":\"\",\"updated_at\":\"\"}}",
|
|
1182
1182
|
};
|
|
1183
|
+
/**
|
|
1184
|
+
* `/rs/{name}` per component WITH a Mzizi Roots (Rust) implementation: the
|
|
1185
|
+
* registry's body ({name, type, target, description, crate, files}) plus
|
|
1186
|
+
* `contract`, the source's CONTRACT clause block or null.
|
|
1187
|
+
*/
|
|
1188
|
+
export const RUST = {
|
|
1189
|
+
"avatar": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"avatar\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"An image element with a fallback for representing the user.\",\"crate\":{\"name\":\"mzizi-ui\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n2-primitives/avatar.rs\",\"type\":\"registry:rust\",\"content\":\"//! AVATAR — N2 primitive, Dioxus.\\n//!\\n//! The Rust sibling of `avatar.tsx`, which wraps Radix's `Avatar.Root`/`Image`/`Fallback`.\\n//! Six pieces: `Avatar`, `AvatarImage`, `AvatarFallback`, `AvatarBadge`, `AvatarGroup`,\\n//! `AvatarGroupCount`.\\n//!\\n//! **`size` is a three-way enum — `\\\"default\\\" | \\\"sm\\\" | \\\"lg\\\"` — not a pixel count.**\\n//! Registry issue #223 calls this out directly: a `size` of `12` handed to a component\\n//! whose variants are `\\\"sm\\\" | \\\"lg\\\"` produced one real bug already (in the sample\\n//! resolver, before this port existed). `AvatarSize` is therefore a closed enum with no\\n//! numeric variant and no `From<u32>` — a pixel value has no way in.\\n//!\\n//! Written against the contract, NOT machine-translated from the `.tsx` (registry\\n//! issue #222, rule 1).\\n//!\\n//! `registry #223`: 30 dependents.\\n\\nuse dioxus::prelude::*;\\n\\n/// Avatar size. See the module doc — this is closed on purpose.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\\npub enum AvatarSize {\\n /// 32px (`size-8`) — the standard avatar.\\n #[default]\\n Default,\\n /// 24px (`size-6`) — for dense lists and inline mentions.\\n Sm,\\n /// 40px (`size-10`) — for a profile header or a solo, prominent avatar.\\n Lg,\\n}\\n\\nimpl AvatarSize {\\n /// The `data-size` value, matching the TypeScript's own strings exactly.\\n const fn slug(self) -> &'static str {\\n match self {\\n Self::Default => \\\"default\\\",\\n Self::Sm => \\\"sm\\\",\\n Self::Lg => \\\"lg\\\",\\n }\\n }\\n\\n /// The square size class used by the LOADING placeholder. Deliberately different\\n /// numbers from the non-loading root below — that asymmetry is in the TypeScript\\n /// today and rule 1 says match the contract, not quietly reconcile something the\\n /// registry issue did not flag.\\n const fn loading_class(self) -> &'static str {\\n match self {\\n Self::Default => \\\"size-9\\\",\\n Self::Sm => \\\"size-7\\\",\\n Self::Lg => \\\"size-12\\\",\\n }\\n }\\n}\\n\\nconst ROOT_BASE: &str = \\\"group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken dark:after:mix-blend-lighten\\\";\\n\\n/// `data-[size=lg]:size-10 data-[size=sm]:size-6` resolved for the given size — Rust\\n/// emits the one that applies rather than the always-present Tailwind arbitrary-attribute\\n/// selector, the same convention `button.rs`/`separator.rs` use.\\nconst fn root_size_class(size: AvatarSize) -> &'static str {\\n match size {\\n AvatarSize::Default => \\\"\\\",\\n AvatarSize::Sm => \\\"size-6\\\",\\n AvatarSize::Lg => \\\"size-10\\\",\\n }\\n}\\n\\n/// Compose the full class string for the (non-loading) avatar root.\\npub fn avatar_variants(size: AvatarSize, extra: &str) -> String {\\n let mut out = String::with_capacity(ROOT_BASE.len() + 32);\\n out.push_str(ROOT_BASE);\\n let sized = root_size_class(size);\\n if !sized.is_empty() {\\n out.push(' ');\\n out.push_str(sized);\\n }\\n if !extra.is_empty() {\\n out.push(' ');\\n out.push_str(extra);\\n }\\n out\\n}\\n\\n/// Props for [`Avatar`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct AvatarProps {\\n /// See [`AvatarSize`].\\n #[props(default)]\\n pub size: AvatarSize,\\n /// A skeleton placeholder instead of the real avatar — used while the identity\\n /// behind it is still loading.\\n #[props(default)]\\n pub loading: bool,\\n /// Extra classes, appended last so a consumer can override.\\n #[props(default)]\\n pub class: String,\\n /// Any other HTML attribute.\\n #[props(extends = GlobalAttributes)]\\n pub attributes: Vec<Attribute>,\\n /// `AvatarImage` / `AvatarFallback` / `AvatarBadge`, when not loading.\\n pub children: Element,\\n}\\n\\n/// A circular identity marker: an image, a text/icon fallback, and an optional badge.\\n///\\n/// ```ignore\\n/// rsx! {\\n/// Avatar {\\n/// AvatarImage { src: \\\"/me.png\\\", alt: \\\"Bryan\\\" }\\n/// AvatarFallback { \\\"BF\\\" }\\n/// }\\n/// }\\n/// ```\\n#[component]\\npub fn Avatar(props: AvatarProps) -> Element {\\n if props.loading {\\n let class = format!(\\n \\\"animate-pulse rounded-full bg-muted {}{}\\\",\\n props.size.loading_class(),\\n if props.class.is_empty() {\\n String::new()\\n } else {\\n format!(\\\" {}\\\", props.class)\\n }\\n );\\n return rsx! {\\n div {\\n \\\"data-slot\\\": \\\"avatar\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/avatar\\\",\\n \\\"data-loading\\\": true,\\n class,\\n ..props.attributes,\\n }\\n };\\n }\\n\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"avatar\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/avatar\\\",\\n \\\"data-size\\\": props.size.slug(),\\n class: avatar_variants(props.size, &props.class),\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\\n/// Props for [`AvatarImage`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct AvatarImageProps {\\n /// Extra classes, appended last so a consumer can override.\\n #[props(default)]\\n pub class: String,\\n /// Any other HTML attribute — `src`, `alt`, `onerror`, and the rest.\\n #[props(extends = GlobalAttributes, extends = img)]\\n pub attributes: Vec<Attribute>,\\n}\\n\\n/// The photo. Renders nothing (falls through to [`AvatarFallback`]) if `src` 404s —\\n/// that behaviour is Radix's `Image` load-state machine in the TypeScript; this port\\n/// relies on the plain `<img>` `onerror` a consumer wires up, since there is no Radix\\n/// underneath here to do it implicitly.\\n#[component]\\npub fn AvatarImage(props: AvatarImageProps) -> Element {\\n const BASE: &str = \\\"aspect-square size-full rounded-full object-cover\\\";\\n rsx! {\\n img {\\n \\\"data-slot\\\": \\\"avatar-image\\\",\\n class: if props.class.is_empty() { BASE.to_string() } else { format!(\\\"{BASE} {}\\\", props.class) },\\n ..props.attributes,\\n }\\n }\\n}\\n\\n/// Props for [`AvatarFallback`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct AvatarFallbackProps {\\n /// Extra classes, appended last so a consumer can override.\\n #[props(default)]\\n pub class: String,\\n /// Any other HTML attribute.\\n #[props(extends = GlobalAttributes)]\\n pub attributes: Vec<Attribute>,\\n /// Initials or an icon.\\n pub children: Element,\\n}\\n\\n/// Initials or an icon, shown while the image is missing or still loading.\\n#[component]\\npub fn AvatarFallback(props: AvatarFallbackProps) -> Element {\\n const BASE: &str = \\\"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs\\\";\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"avatar-fallback\\\",\\n class: if props.class.is_empty() { BASE.to_string() } else { format!(\\\"{BASE} {}\\\", props.class) },\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\\n/// Props for [`AvatarBadge`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct AvatarBadgeProps {\\n /// Extra classes, appended last so a consumer can override.\\n #[props(default)]\\n pub class: String,\\n /// Any other HTML attribute.\\n #[props(extends = GlobalAttributes)]\\n pub attributes: Vec<Attribute>,\\n /// The badge's icon or dot.\\n pub children: Element,\\n}\\n\\n/// A small status marker pinned to the avatar's bottom-right corner (e.g. an\\n/// online/offline dot or a platform icon). Sized off the ANCESTOR avatar's `size` via\\n/// the `group-data-[size=…]/avatar` selectors — it has no size prop of its own.\\n#[component]\\npub fn AvatarBadge(props: AvatarBadgeProps) -> Element {\\n const BASE: &str = \\\"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2 group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2\\\";\\n rsx! {\\n span {\\n \\\"data-slot\\\": \\\"avatar-badge\\\",\\n class: if props.class.is_empty() { BASE.to_string() } else { format!(\\\"{BASE} {}\\\", props.class) },\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\\n/// Props for [`AvatarGroup`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct AvatarGroupProps {\\n /// Extra classes, appended last so a consumer can override.\\n #[props(default)]\\n pub class: String,\\n /// Any other HTML attribute.\\n #[props(extends = GlobalAttributes)]\\n pub attributes: Vec<Attribute>,\\n /// The member [`Avatar`]s, and optionally a trailing [`AvatarGroupCount`].\\n pub children: Element,\\n}\\n\\n/// Overlapping stack of avatars, each ringed to separate it from its neighbours.\\n#[component]\\npub fn AvatarGroup(props: AvatarGroupProps) -> Element {\\n const BASE: &str = \\\"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background\\\";\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"avatar-group\\\",\\n class: if props.class.is_empty() { BASE.to_string() } else { format!(\\\"{BASE} {}\\\", props.class) },\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\\n/// Props for [`AvatarGroupCount`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct AvatarGroupCountProps {\\n /// Extra classes, appended last so a consumer can override.\\n #[props(default)]\\n pub class: String,\\n /// Any other HTML attribute.\\n #[props(extends = GlobalAttributes)]\\n pub attributes: Vec<Attribute>,\\n /// The overflow count, e.g. `\\\"+3\\\"`.\\n pub children: Element,\\n}\\n\\n/// The trailing \\\"+N\\\" tile at the end of an [`AvatarGroup`], sized off the GROUP's own\\n/// size via `group-has-data-[size=…]/avatar-group` — the group, not an individual\\n/// avatar, since this element has no avatar of its own to key off.\\n#[component]\\npub fn AvatarGroupCount(props: AvatarGroupCountProps) -> Element {\\n const BASE: &str = \\\"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3\\\";\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"avatar-group-count\\\",\\n class: if props.class.is_empty() { BASE.to_string() } else { format!(\\\"{BASE} {}\\\", props.class) },\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1190
|
+
"badge": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"badge\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Displays a badge or a component that looks like a badge.\",\"crate\":{\"name\":\"mzizi-ui\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n2-primitives/badge.rs\",\"type\":\"registry:rust\",\"content\":\"//! BADGE — N2 primitive, Dioxus.\\n//!\\n//! The Rust sibling of `badge.tsx`, sharing its contract: the same six variants, the same\\n//! `data-slot` / `data-variant` attributes, and the same Tailwind classes, so one stylesheet\\n//! serves both targets.\\n//!\\n//! A badge is not interactive on its own, so it carries no touch-target floor — the `[a]:`\\n//! prefixed hover classes only apply when a consumer wraps it in a link, which is when it\\n//! becomes a target and inherits the link's hit area.\\n\\nuse dioxus::prelude::*;\\n\\n/// Visual treatment. Matches `badgeVariants` in `badge.tsx` exactly.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\\npub enum BadgeVariant {\\n /// Solid primary fill.\\n #[default]\\n Default,\\n /// Solid secondary fill.\\n Secondary,\\n /// Destructive state — error, revoked, failed.\\n Destructive,\\n /// Bordered, low-emphasis.\\n Outline,\\n /// No fill until hovered.\\n Ghost,\\n /// Renders as an inline text link.\\n Link,\\n}\\n\\n/// The base classes every badge carries.\\nconst BASE: &str = \\\"h-5 gap-1 rounded-md border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive overflow-hidden group/badge\\\";\\n\\nimpl BadgeVariant {\\n /// The Tailwind classes for this variant.\\n pub const fn classes(self) -> &'static str {\\n match self {\\n Self::Default => \\\"bg-primary text-primary-foreground [a]:hover:bg-primary/80\\\",\\n Self::Secondary => \\\"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80\\\",\\n Self::Destructive => {\\n \\\"bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20\\\"\\n }\\n Self::Outline => {\\n \\\"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground bg-input/30\\\"\\n }\\n Self::Ghost => \\\"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50\\\",\\n Self::Link => \\\"text-primary underline-offset-4 hover:underline\\\",\\n }\\n }\\n\\n /// The `data-variant` value.\\n pub const fn slug(self) -> &'static str {\\n match self {\\n Self::Default => \\\"default\\\",\\n Self::Secondary => \\\"secondary\\\",\\n Self::Destructive => \\\"destructive\\\",\\n Self::Outline => \\\"outline\\\",\\n Self::Ghost => \\\"ghost\\\",\\n Self::Link => \\\"link\\\",\\n }\\n }\\n}\\n\\n/// Compose the full class string for a badge.\\npub fn badge_variants(variant: BadgeVariant, extra: &str) -> String {\\n let mut out = String::with_capacity(BASE.len() + 128);\\n out.push_str(BASE);\\n out.push(' ');\\n out.push_str(variant.classes());\\n if !extra.is_empty() {\\n out.push(' ');\\n out.push_str(extra);\\n }\\n out\\n}\\n\\n/// Props for [`Badge`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct BadgeProps {\\n /// Visual treatment.\\n #[props(default)]\\n pub variant: BadgeVariant,\\n /// Extra classes, appended last so a consumer can override.\\n #[props(default)]\\n pub class: String,\\n /// Any other HTML attribute.\\n #[props(extends = GlobalAttributes)]\\n pub attributes: Vec<Attribute>,\\n /// Badge label.\\n pub children: Element,\\n}\\n\\n/// A small status or category label.\\n#[component]\\npub fn Badge(props: BadgeProps) -> Element {\\n rsx! {\\n span {\\n \\\"data-slot\\\": \\\"badge\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/badge\\\",\\n \\\"data-variant\\\": props.variant.slug(),\\n class: badge_variants(props.variant, &props.class),\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1191
|
+
"button": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"button\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Displays a button or a component that looks like a button.\",\"crate\":{\"name\":\"mzizi-ui\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n2-primitives/button.rs\",\"type\":\"registry:rust\",\"content\":\"//! BUTTON — N2 primitive, Dioxus.\\n//!\\n//! The Rust sibling of `button.tsx`. They share a CONTRACT and a token set, not a source\\n//! file: same variants, same sizes, same `data-slot` / `data-variant` / `data-size`\\n//! attributes, same Tailwind classes, so a Dioxus app and a React app render the same\\n//! button from the same stylesheet.\\n//!\\n//! Written against the contract, NOT machine-translated from the `.tsx`. A mechanical\\n//! TSX→RSX pass carries every defect in the original across while `cargo check` waves it\\n//! through, because a faithful port of a broken component still compiles.\\n//!\\n//! Token compliance, identical to the TypeScript:\\n//! * Radius `rounded-full` — buttons are ALWAYS pill-shaped per brand (CLAUDE.md §7.5)\\n//! * `--ring` token for the focus ring; semantic colour tokens throughout\\n//! * `data-slot` present for CSS targeting and testing\\n//!\\n//! No harness. Primitives are too low-level for observability wiring — brand components\\n//! that USE a button wire in at their own level (CLAUDE.md §6.2).\\n\\nuse dioxus::prelude::*;\\n\\n/// Visual treatment. The set matches `buttonVariants` in `button.tsx` exactly; a variant\\n/// that exists on one target and not the other is a broken contract, not a feature.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\\npub enum ButtonVariant {\\n /// Solid primary fill — the default call to action.\\n #[default]\\n Default,\\n /// Bordered, transparent-ish fill.\\n Outline,\\n /// Solid secondary fill.\\n Secondary,\\n /// No fill until hovered.\\n Ghost,\\n /// Destructive action — delete, remove, revoke.\\n Destructive,\\n /// Renders as an inline text link.\\n Link,\\n}\\n\\n/// Control size. `Default` and `Lg` are 56px; `Sm` is the 48px floor.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\\npub enum ButtonSize {\\n /// 56px tall — the brand standard.\\n #[default]\\n Default,\\n /// 48px tall — the minimum allowed for a touch target.\\n Sm,\\n /// 56px tall with wider padding.\\n Lg,\\n /// 56px square, for an icon alone.\\n Icon,\\n /// 48px square, for an icon alone.\\n IconSm,\\n}\\n\\n/// The base classes every button carries, whatever its variant or size.\\nconst BASE: &str = \\\"focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-full border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-[3px] aria-invalid:ring-[3px] [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none\\\";\\n\\nimpl ButtonVariant {\\n /// The Tailwind classes for this variant.\\n ///\\n /// A `match` is the whole variant system here — Rust's exhaustiveness check does the job\\n /// `class-variance-authority` does in TypeScript, at compile time and with no dependency.\\n /// Adding a variant without giving it classes will not build.\\n pub const fn classes(self) -> &'static str {\\n match self {\\n Self::Default => \\\"bg-primary text-primary-foreground hover:bg-primary/80\\\",\\n Self::Outline => {\\n \\\"border-border bg-input/30 hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground\\\"\\n }\\n Self::Secondary => {\\n \\\"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground\\\"\\n }\\n Self::Ghost => {\\n \\\"hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground\\\"\\n }\\n Self::Destructive => {\\n \\\"bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30\\\"\\n }\\n Self::Link => \\\"text-primary underline-offset-4 hover:underline\\\",\\n }\\n }\\n\\n /// The `data-variant` value, matching what the React component emits so one stylesheet\\n /// and one test selector serve both targets.\\n pub const fn slug(self) -> &'static str {\\n match self {\\n Self::Default => \\\"default\\\",\\n Self::Outline => \\\"outline\\\",\\n Self::Secondary => \\\"secondary\\\",\\n Self::Ghost => \\\"ghost\\\",\\n Self::Destructive => \\\"destructive\\\",\\n Self::Link => \\\"link\\\",\\n }\\n }\\n}\\n\\nimpl ButtonSize {\\n /// The Tailwind classes for this size.\\n pub const fn classes(self) -> &'static str {\\n match self {\\n Self::Default => {\\n \\\"h-14 gap-2 px-5 has-data-[icon=inline-end]:pr-4 has-data-[icon=inline-start]:pl-4\\\"\\n }\\n Self::Sm => {\\n \\\"h-12 gap-1.5 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3\\\"\\n }\\n Self::Lg => {\\n \\\"h-14 gap-2 px-6 has-data-[icon=inline-end]:pr-5 has-data-[icon=inline-start]:pl-5\\\"\\n }\\n Self::Icon => \\\"size-14\\\",\\n Self::IconSm => \\\"size-12\\\",\\n }\\n }\\n\\n /// The `data-size` value.\\n pub const fn slug(self) -> &'static str {\\n match self {\\n Self::Default => \\\"default\\\",\\n Self::Sm => \\\"sm\\\",\\n Self::Lg => \\\"lg\\\",\\n Self::Icon => \\\"icon\\\",\\n Self::IconSm => \\\"icon-sm\\\",\\n }\\n }\\n}\\n\\n/// Compose the full class string for a button.\\npub fn button_variants(variant: ButtonVariant, size: ButtonSize, extra: &str) -> String {\\n let mut out = String::with_capacity(BASE.len() + 256);\\n out.push_str(BASE);\\n out.push(' ');\\n out.push_str(variant.classes());\\n out.push(' ');\\n out.push_str(size.classes());\\n if !extra.is_empty() {\\n out.push(' ');\\n out.push_str(extra);\\n }\\n out\\n}\\n\\n/// Props for [`Button`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct ButtonProps {\\n /// Visual treatment.\\n #[props(default)]\\n pub variant: ButtonVariant,\\n /// Control size.\\n #[props(default)]\\n pub size: ButtonSize,\\n /// Extra classes, appended last so a consumer can override.\\n #[props(default)]\\n pub class: String,\\n /// Any other HTML attribute — `onclick`, `disabled`, `aria-*`, and the rest.\\n #[props(extends = GlobalAttributes, extends = button)]\\n pub attributes: Vec<Attribute>,\\n /// Button label or icon.\\n pub children: Element,\\n}\\n\\n/// A pill-shaped button.\\n///\\n/// ```ignore\\n/// rsx! { Button { variant: ButtonVariant::Outline, \\\"Cancel\\\" } }\\n/// ```\\n#[component]\\npub fn Button(props: ButtonProps) -> Element {\\n rsx! {\\n button {\\n \\\"data-slot\\\": \\\"button\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/button\\\",\\n \\\"data-variant\\\": props.variant.slug(),\\n \\\"data-size\\\": props.size.slug(),\\n class: button_variants(props.variant, props.size, &props.class),\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1192
|
+
"card": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"card\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Displays a card with header, content, and footer.\",\"crate\":{\"name\":\"mzizi-ui\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n2-primitives/card.rs\",\"type\":\"registry:rust\",\"content\":\"//! CARD — N2 primitive, Dioxus.\\n//!\\n//! The Rust sibling of `card.tsx`, sharing its contract: the same seven slots, the same\\n//! `data-slot` names, the same radius tokens, and the same built-in loading skeleton.\\n//!\\n//! The card is the most composed primitive in the system — every brand component that shows\\n//! content in a container ultimately renders one — which is why the slots matter more than\\n//! the container. A Dioxus consumer composing `CardHeader` / `CardTitle` / `CardContent`\\n//! produces markup a React-authored stylesheet already styles, because the `data-slot` names\\n//! are the contract.\\n//!\\n//! Token compliance, identical to the TypeScript:\\n//! * `rounded-[var(--radius-lg,14px)]` — the Mzizi card radius, with the documented `var()`\\n//! fallback form so the component still has a radius in an app that has not defined the\\n//! custom property (CLAUDE.md §7.6)\\n//! * `ring-1 ring-foreground/10` for the border, consistent across themes\\n\\nuse dioxus::prelude::*;\\n\\n/// Card density.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\\npub enum CardSize {\\n /// Standard padding and gaps.\\n #[default]\\n Default,\\n /// Tighter padding and gaps.\\n Sm,\\n}\\n\\nimpl CardSize {\\n /// The `data-size` value, which the descendant slots key their padding off via\\n /// `group-data-[size=sm]/card:`.\\n pub const fn slug(self) -> &'static str {\\n match self {\\n Self::Default => \\\"default\\\",\\n Self::Sm => \\\"sm\\\",\\n }\\n }\\n}\\n\\nconst CARD_BASE: &str = \\\"group/card flex flex-col gap-6 overflow-hidden rounded-[var(--radius-lg,14px)] bg-card py-6 text-sm text-card-foreground ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-[var(--radius-md,12px)] *:[img:last-child]:rounded-b-[var(--radius-md,12px)]\\\";\\n\\nconst CARD_LOADING: &str = \\\"group/card flex animate-pulse flex-col gap-4 overflow-hidden rounded-[var(--radius-lg,14px)] bg-card p-6 text-sm ring-1 ring-foreground/10\\\";\\n\\n/// Join a base class string with a consumer's extra classes.\\nfn join(base: &str, extra: &str) -> String {\\n if extra.is_empty() {\\n base.to_string()\\n } else {\\n format!(\\\"{base} {extra}\\\")\\n }\\n}\\n\\n/// Props for [`Card`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct CardProps {\\n /// Density.\\n #[props(default)]\\n pub size: CardSize,\\n /// Render the built-in skeleton instead of the children.\\n ///\\n /// Every component from N2 up renders its own skeleton rather than leaving the decision\\n /// to the page, so a slow section degrades in place instead of collapsing the layout.\\n #[props(default = false)]\\n pub loading: bool,\\n /// Extra classes, appended last so a consumer can override.\\n #[props(default)]\\n pub class: String,\\n /// Any other HTML attribute.\\n #[props(extends = GlobalAttributes)]\\n pub attributes: Vec<Attribute>,\\n /// Card contents — normally `CardHeader`, `CardContent`, `CardFooter`.\\n pub children: Element,\\n}\\n\\n/// A content container.\\n#[component]\\npub fn Card(props: CardProps) -> Element {\\n if props.loading {\\n let class = join(\\n CARD_LOADING,\\n &if matches!(props.size, CardSize::Sm) {\\n join(\\\"gap-3 p-4\\\", &props.class)\\n } else {\\n props.class.clone()\\n },\\n );\\n return rsx! {\\n div {\\n \\\"data-slot\\\": \\\"card\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/card\\\",\\n \\\"data-loading\\\": \\\"true\\\",\\n class,\\n div { class: \\\"h-4 w-2/3 rounded bg-muted\\\" }\\n div { class: \\\"h-3 w-full rounded bg-muted\\\" }\\n div { class: \\\"h-3 w-4/5 rounded bg-muted\\\" }\\n }\\n };\\n }\\n\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"card\\\",\\n \\\"data-size\\\": props.size.slug(),\\n class: join(CARD_BASE, &props.class),\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\\n/// Props shared by every plain card slot.\\n#[derive(Props, Clone, PartialEq)]\\npub struct CardSlotProps {\\n /// Extra classes, appended last.\\n #[props(default)]\\n pub class: String,\\n /// Any other HTML attribute.\\n #[props(extends = GlobalAttributes)]\\n pub attributes: Vec<Attribute>,\\n /// Slot contents.\\n pub children: Element,\\n}\\n\\n/// The card's header region.\\n#[component]\\npub fn CardHeader(props: CardSlotProps) -> Element {\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"card-header\\\",\\n class: join(\\\"group/card-header @container/card-header grid auto-rows-min items-start gap-2 rounded-t-[var(--radius-md,12px)] px-6 group-data-[size=sm]/card:px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4\\\", &props.class),\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\\n/// The card's title.\\n#[component]\\npub fn CardTitle(props: CardSlotProps) -> Element {\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"card-title\\\",\\n class: join(\\\"text-base font-medium\\\", &props.class),\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\\n/// Supporting text under the title.\\n#[component]\\npub fn CardDescription(props: CardSlotProps) -> Element {\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"card-description\\\",\\n class: join(\\\"text-sm text-muted-foreground\\\", &props.class),\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\\n/// An action pinned to the header's trailing edge.\\n#[component]\\npub fn CardAction(props: CardSlotProps) -> Element {\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"card-action\\\",\\n class: join(\\\"col-start-2 row-span-2 row-start-1 self-start justify-self-end\\\", &props.class),\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\\n/// The card's main body.\\n#[component]\\npub fn CardContent(props: CardSlotProps) -> Element {\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"card-content\\\",\\n class: join(\\\"px-6 group-data-[size=sm]/card:px-4\\\", &props.class),\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\\n/// The card's footer region.\\n#[component]\\npub fn CardFooter(props: CardSlotProps) -> Element {\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"card-footer\\\",\\n class: join(\\\"flex items-center rounded-b-[var(--radius-md,12px)] px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4\\\", &props.class),\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1193
|
+
"chart": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"chart\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Beautiful charts built using Recharts. Copy and paste into your apps.\",\"crate\":{\"name\":\"mzizi-ui\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n2-primitives/chart.rs\",\"type\":\"registry:rust\",\"content\":\"//! CHART — N2 primitive, Dioxus. Container/contract only.\\n//!\\n//! `chart.tsx` is a wrapper around Recharts, a JavaScript charting runtime: `ChartStyle`\\n//! injects `<style>` from a `ChartConfig` keyed to Recharts' theme selectors, and\\n//! `ChartTooltipContent`/`ChartLegendContent` format Recharts' own tooltip/legend payload\\n//! shapes. None of that has a Rust equivalent to port against — there is no charting\\n//! runtime on this target, and registry issue #223 explicitly ruled out reimplementing\\n//! Recharts in Rust as out of scope for this wave. That is the design decision #223 left\\n//! open (container-only port vs. backing this with the existing `canvas-chart` primitive);\\n//! this resolves it as the container-only port, since backing a *contract port* with a\\n//! *different, unrelated primitive* would not be porting `chart.tsx` at all.\\n//!\\n//! What IS ported is the one piece with no Recharts dependency: `ChartContainer`'s own\\n//! wrapper contract — `data-slot`, the `data-chart` id, the wrapper class list (including\\n//! the Recharts CSS-selector overrides, which are inert without Recharts but are still part\\n//! of the exact class-string contract), and the loading skeleton, which is plain markup.\\n//!\\n//! NOT ported: `ChartConfig`, `ChartStyle`, `ChartTooltip`, `ChartTooltipContent`,\\n//! `ChartLegend`, `ChartLegendContent`. Backing them with a real Rust/WASM charting library\\n//! is future work, not a gap in this port.\\n//!\\n//! One more deliberate divergence from `useId()`: the TypeScript generates a `chart-<id>`\\n//! id automatically via `React.useId()` when none is passed. Dioxus has no equivalent hook\\n//! that guarantees per-instance uniqueness, and a fake one would be worse than an explicit\\n//! requirement — so `id` is a required prop here, not optional.\\n//!\\n//! Written against the contract, NOT machine-translated from the `.tsx` (registry issue\\n//! #222, rule 1).\\n//!\\n//! `registry #223`: the sixth and last primitive in wave 1.\\n\\nuse dioxus::prelude::*;\\n\\n/// The wrapper classes for a rendered chart, including the Recharts CSS-selector\\n/// overrides — verbatim from the TypeScript even though they target class names\\n/// Recharts itself emits at runtime, which this port has no equivalent of. They are\\n/// part of the class-string contract regardless of whether anything on this target\\n/// currently renders a `.recharts-*` element.\\nconst BASE: &str = \\\"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden\\\";\\n\\n/// The loading-skeleton classes — the one branch of `chart.tsx` with no Recharts\\n/// dependency at all, so the one branch this container-only port can fully match.\\nconst LOADING_BASE: &str = \\\"flex aspect-video animate-pulse items-end justify-center gap-2 p-6\\\";\\n\\n/// Compose the full class string for a rendered (non-loading) chart container.\\npub fn chart_variants(extra: &str) -> String {\\n let mut out = String::with_capacity(BASE.len() + 32);\\n out.push_str(BASE);\\n if !extra.is_empty() {\\n out.push(' ');\\n out.push_str(extra);\\n }\\n out\\n}\\n\\n/// Compose the full class string for the loading skeleton.\\npub fn chart_loading_variants(extra: &str) -> String {\\n let mut out = String::with_capacity(LOADING_BASE.len() + 32);\\n out.push_str(LOADING_BASE);\\n if !extra.is_empty() {\\n out.push(' ');\\n out.push_str(extra);\\n }\\n out\\n}\\n\\n/// Seven bar heights for the loading skeleton. `chart.tsx` randomizes these per\\n/// render (`20 + Math.random() * 60`); the contract is the structure and class\\n/// list, not the exact animation, so this port uses a fixed, varied sequence\\n/// rather than pulling in a random-number dependency for cosmetic motion.\\nconst LOADING_BAR_HEIGHTS: [u8; 7] = [45, 70, 35, 80, 55, 65, 40];\\n\\n/// Props for [`Chart`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct ChartProps {\\n /// Renders the loading skeleton instead of the chart container.\\n #[props(default)]\\n pub loading: bool,\\n /// The `data-chart` id. Required — see the module doc comment on why this\\n /// port does not attempt to replicate `useId()`'s auto-generation.\\n pub id: String,\\n /// Extra classes, appended last so a consumer can override.\\n #[props(default)]\\n pub class: String,\\n /// Any other HTML attribute.\\n #[props(extends = GlobalAttributes)]\\n pub attributes: Vec<Attribute>,\\n /// The chart's rendered content. This primitive has no charting runtime of\\n /// its own — whatever draws the chart is the caller's responsibility.\\n pub children: Element,\\n}\\n\\n/// A chart container. Container/contract only — see the module doc comment.\\n#[component]\\npub fn Chart(props: ChartProps) -> Element {\\n if props.loading {\\n return rsx! {\\n div {\\n \\\"data-slot\\\": \\\"chart\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/chart\\\",\\n \\\"data-loading\\\": \\\"true\\\",\\n class: chart_loading_variants(&props.class),\\n ..props.attributes,\\n for height in LOADING_BAR_HEIGHTS {\\n div {\\n class: \\\"flex-1 rounded-t bg-muted\\\",\\n style: \\\"height: {height}%\\\",\\n }\\n }\\n }\\n };\\n }\\n\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"chart\\\",\\n \\\"data-chart\\\": \\\"chart-{props.id}\\\",\\n class: chart_variants(&props.class),\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1194
|
+
"input": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"input\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Displays a form input field.\",\"crate\":{\"name\":\"mzizi-ui\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n2-primitives/input.rs\",\"type\":\"registry:rust\",\"content\":\"//! INPUT — N2 primitive, Dioxus.\\n//!\\n//! The Rust sibling of `input.tsx`. No variants, no sub-components — one element, one\\n//! static class string, and the native `type` attribute. The class list is the whole\\n//! contract: pill radius (`rounded-full`) and a 48px touch target (`h-12`) per brand,\\n//! the `--ring` focus ring, and `aria-invalid` styling that keys off the browser's own\\n//! validation state rather than a prop.\\n//!\\n//! Written against the contract, NOT machine-translated from the `.tsx` (registry\\n//! issue #222, rule 1).\\n//!\\n//! `registry #223`: 43 dependents — the second-heaviest primitive in wave 1.\\n\\nuse dioxus::prelude::*;\\n\\n/// The classes every input carries.\\nconst BASE: &str = \\\"h-12 w-full min-w-0 rounded-full border border-input bg-input/30 px-4 py-1 text-base transition-colors outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 md:text-sm dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40\\\";\\n\\n/// Compose the full class string for an input.\\npub fn input_variants(extra: &str) -> String {\\n let mut out = String::with_capacity(BASE.len() + 32);\\n out.push_str(BASE);\\n if !extra.is_empty() {\\n out.push(' ');\\n out.push_str(extra);\\n }\\n out\\n}\\n\\n/// Props for [`Input`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct InputProps {\\n /// Extra classes, appended last so a consumer can override.\\n #[props(default)]\\n pub class: String,\\n /// Any other HTML attribute — `r#type`, `value`, `placeholder`, `disabled`,\\n /// `aria-invalid`, and the rest. `type` is deliberately not a dedicated prop: the\\n /// TypeScript passes it straight through too, and giving it special handling here\\n /// would be a second place a new input type needs teaching.\\n #[props(extends = GlobalAttributes, extends = input)]\\n pub attributes: Vec<Attribute>,\\n}\\n\\n/// A single-line text input.\\n///\\n/// ```ignore\\n/// rsx! { Input { r#type: \\\"email\\\", placeholder: \\\"you@example.com\\\" } }\\n/// ```\\n#[component]\\npub fn Input(props: InputProps) -> Element {\\n rsx! {\\n input {\\n \\\"data-slot\\\": \\\"input\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/input\\\",\\n class: input_variants(&props.class),\\n ..props.attributes,\\n }\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1195
|
+
"label": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"label\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Renders an accessible label associated with controls.\",\"crate\":{\"name\":\"mzizi-ui\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n2-primitives/label.rs\",\"type\":\"registry:rust\",\"content\":\"//! LABEL — N2 primitive, Dioxus.\\n//!\\n//! The Rust sibling of `label.tsx`, which wraps Radix's `Label.Root`. The contract that\\n//! matters here is two states that come from OUTSIDE this element, not from its own\\n//! props: `group-data-[disabled=true]` (an ancestor group marked disabled) and\\n//! `peer-disabled` (a sibling form control that is itself disabled). Both are plain CSS\\n//! attribute/pseudo selectors, so they need no Rust-side state at all — the classes are\\n//! static and the browser does the rest, exactly as in the TypeScript.\\n//!\\n//! Written against the contract, NOT machine-translated from the `.tsx` (registry\\n//! issue #222, rule 1).\\n//!\\n//! `registry #223`: 31 dependents.\\n\\nuse dioxus::prelude::*;\\n\\n/// The classes every label carries. No variants — `label.tsx` has none either, only the\\n/// `htmlFor` association (native `for`/`html_for` in Dioxus) and these two external-state\\n/// selectors.\\nconst BASE: &str = \\\"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50\\\";\\n\\n/// Compose the full class string for a label.\\npub fn label_variants(extra: &str) -> String {\\n let mut out = String::with_capacity(BASE.len() + 32);\\n out.push_str(BASE);\\n if !extra.is_empty() {\\n out.push(' ');\\n out.push_str(extra);\\n }\\n out\\n}\\n\\n/// Props for [`Label`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct LabelProps {\\n /// Extra classes, appended last so a consumer can override.\\n #[props(default)]\\n pub class: String,\\n /// Any other HTML attribute — `r#for`/`html_for` for the form-control association,\\n /// `onclick`, and the rest.\\n #[props(extends = GlobalAttributes, extends = label)]\\n pub attributes: Vec<Attribute>,\\n /// Label text or inline content.\\n pub children: Element,\\n}\\n\\n/// A form-control label.\\n///\\n/// ```ignore\\n/// rsx! { Label { r#for: \\\"email\\\", \\\"Email address\\\" } }\\n/// ```\\n#[component]\\npub fn Label(props: LabelProps) -> Element {\\n rsx! {\\n label {\\n \\\"data-slot\\\": \\\"label\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/label\\\",\\n class: label_variants(&props.class),\\n ..props.attributes,\\n {props.children}\\n }\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1196
|
+
"mzizi-a11y-audit": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-a11y-audit\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"Runtime accessibility compliance checker. Scans rendered DOM for WCAG violations: missing ARIA labels, insufficient contrast, missing alt text, broken focus order, touch target sizes. Can run on-demand or continuously in development. Competitors: Lighthouse, axe-core.\",\"crate\":{\"name\":\"mzizi-assurance\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n8-assurance/mzizi-a11y-audit.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N8 assurance — runtime accessibility rules.\\n//!\\n//! The Rust implementation of `mzizi-a11y-audit`. The rules, the levels, the\\n//! selector and the score; not the DOM walk. The host observes the page and hands\\n//! over a list of [`ObservedNode`] in document order, which is the only step that\\n//! needs a browser.\\n//!\\n//! Document order matters and is part of the contract: [`Rule::HeadingOrder`]\\n//! reads it. Hand nodes over in the order `querySelectorAll(\\\"*\\\")` yields them, not\\n//! sorted or filtered.\\n//!\\n//! # Four defects in the TypeScript, fixed here\\n//!\\n//! **1. One element could report at most ONE violation.** The `.ts` chains its\\n//! rules with `else if`, so an `<img>` with no alt is never checked for anything\\n//! else, and a `<button>` with no accessible name is never checked for touch\\n//! target size. The rules are independent facts about an element; chaining them\\n//! means the *first* finding hides the rest, and a button with no name is exactly\\n//! the button most likely to also be too small. Every rule is evaluated here.\\n//!\\n//! **2. A conformant button never counted as a pass.** Falling into the\\n//! touch-target branch and finding nothing wrong incremented nothing — `passes++`\\n//! lived only in the branches an interactive element could not reach. So a page\\n//! of correct buttons scored *lower* than a page of `<div>`s, and the one number\\n//! anybody looks at moved the wrong way as accessibility improved. An element\\n//! passes here when it produced no violations, which is the only definition that\\n//! cannot invert.\\n//!\\n//! **3. The heading-order rule was a comment.** `// Rule: Headings should be in\\n//! order` sat above a branch that matched `H1`–`H6` and then did nothing but count\\n//! a pass. A stated rule that evaluates nothing reads as coverage on a dashboard\\n//! and is worse than an absent one. Implemented — see [`Rule::HeadingOrder`].\\n//!\\n//! **4. The touch-target rule measured the wrong box.** `getBoundingClientRect()`\\n//! is the *visual* box, and Mzizi's control scale is deliberately dense — `h-8` /\\n//! `h-9` / `h-10`, so 32-40px (CLAUDE.md §8.2). Measuring the visual box therefore\\n//! flagged every correctly-built Mzizi control as a moderate violation: the design\\n//! system failed its own audit universally, which is the fastest way to teach\\n//! everyone to ignore a rule. §8.2 says a dense control earns its hit area through\\n//! surrounding spacing or padding beyond the visual box, so the hit area is what\\n//! the rule is about. The host supplies [`ObservedNode::hit_box`] and is expected\\n//! to include that padding.\\n//!\\n//! # Two things recorded rather than changed\\n//!\\n//! **`<a>` is held to a size but not to a name.** `button-name` covers `<button>`\\n//! and `role=\\\"button\\\"`; `touch-target` additionally covers `<a>`. So a link with\\n//! no accessible name passes. That is a coverage gap in the rule set, not a defect\\n//! in this port, and adding a `link-name` rule would silently change what every\\n//! existing consumer's score means. Left as it is, said out loud.\\n//!\\n//! **The node guess can only ever answer 2, 3 or 6.** `guessNodeFromSlot` is a\\n//! string heuristic over the slot name, so a violation in an N7 shell component is\\n//! labelled N6 and routed to the wrong owner — and N1, N4, N5 and N8-N12 are\\n//! unreachable answers. The registry knows the real node, so [`resolve_node`]\\n//! prefers a lookup the host supplies and falls back to the heuristic only when\\n//! there is no entry. The heuristic is kept because a consumer auditing their own\\n//! app has no Mzizi registry to consult.\\n\\nuse std::collections::{BTreeMap, BTreeSet};\\n\\n/// How much an accessibility violation matters.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum A11yLevel {\\n /// Cosmetic or advisory.\\n Minor,\\n /// Degrades the experience for assistive technology.\\n Moderate,\\n /// Blocks a common task.\\n Serious,\\n /// Makes the element unusable.\\n Critical,\\n}\\n\\nimpl A11yLevel {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Critical => \\\"critical\\\",\\n Self::Serious => \\\"serious\\\",\\n Self::Moderate => \\\"moderate\\\",\\n Self::Minor => \\\"minor\\\",\\n }\\n }\\n}\\n\\n/// Which rule produced a finding.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum Rule {\\n /// An image with no `alt` attribute at all.\\n ImgAlt,\\n /// A button with no accessible name.\\n ButtonName,\\n /// An interactive control whose hit area is below the floor.\\n TouchTarget,\\n /// A heading that skips a level.\\n HeadingOrder,\\n}\\n\\nimpl Rule {\\n /// The wire spelling, matching the `.ts` `rule` strings.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::ImgAlt => \\\"img-alt\\\",\\n Self::ButtonName => \\\"button-name\\\",\\n Self::TouchTarget => \\\"touch-target\\\",\\n Self::HeadingOrder => \\\"heading-order\\\",\\n }\\n }\\n\\n /// Every rule this core evaluates.\\n #[must_use]\\n pub fn all() -> BTreeSet<Self> {\\n [\\n Self::ImgAlt,\\n Self::ButtonName,\\n Self::TouchTarget,\\n Self::HeadingOrder,\\n ]\\n .into_iter()\\n .collect()\\n }\\n}\\n\\n/// The measured hit area of a control, in CSS pixels.\\n///\\n/// This is the *interactive* area — the visual box plus any padding that extends\\n/// what a finger can land on — not `getBoundingClientRect()` on the painted\\n/// element. See the module docs, defect 4.\\n#[derive(Debug, Clone, Copy, PartialEq)]\\npub struct HitBox {\\n /// Width in CSS pixels.\\n pub width: f64,\\n /// Height in CSS pixels.\\n pub height: f64,\\n}\\n\\n/// One element as the host observed it.\\n///\\n/// Every field is a plain value rather than a live DOM reference, which is what\\n/// lets the rules run in a Worker, in CI against server-rendered HTML, or in a\\n/// test. It also sidesteps a real crash in the `.ts`: `el.className` is an\\n/// `SVGAnimatedString` on SVG elements, so `.split(\\\" \\\")` throws on any SVG element\\n/// that reaches the selector builder.\\n///\\n/// `PartialEq` without `Eq`, because [`HitBox`] holds `f64` and floats have no\\n/// total equality — the same reason [`f64::NAN`] is not equal to itself.\\n#[derive(Debug, Clone, PartialEq, Default)]\\npub struct ObservedNode {\\n /// Uppercase tag name, e.g. `BUTTON`.\\n pub tag_name: String,\\n /// The `id`, when present.\\n pub id: Option<String>,\\n /// The first class, when present.\\n pub first_class: Option<String>,\\n /// The `data-slot` value.\\n pub data_slot: Option<String>,\\n /// The `data-portal` backlink.\\n pub data_portal: Option<String>,\\n /// The `alt` attribute. `Some(\\\"\\\")` is a decorative image and passes; `None`\\n /// means the attribute is absent, which does not.\\n pub alt: Option<String>,\\n /// The `aria-label`.\\n pub aria_label: Option<String>,\\n /// The `aria-labelledby`.\\n pub aria_labelledby: Option<String>,\\n /// The `role`.\\n pub role: Option<String>,\\n /// Rendered text content.\\n pub text: Option<String>,\\n /// Whether the element occupies space. The `.ts` uses `offsetHeight > 0`,\\n /// which reports true for `visibility: hidden` and `opacity: 0` — so a hidden\\n /// control is still audited. Named `visible` because that is what the rule\\n /// wants; supplying the honest answer is the host's job.\\n pub visible: bool,\\n /// The interactive area, when the host could measure it.\\n pub hit_box: Option<HitBox>,\\n}\\n\\nimpl ObservedNode {\\n /// Whether this element is a button for the purposes of [`Rule::ButtonName`].\\n #[must_use]\\n pub fn is_button(&self) -> bool {\\n self.tag_name.eq_ignore_ascii_case(\\\"button\\\") || self.role.as_deref() == Some(\\\"button\\\")\\n }\\n\\n /// Whether this element is a touch target for [`Rule::TouchTarget`].\\n #[must_use]\\n pub fn is_touch_target(&self) -> bool {\\n self.is_button() || self.tag_name.eq_ignore_ascii_case(\\\"a\\\")\\n }\\n\\n /// The name a screen reader would announce, if any.\\n ///\\n /// `role` is NOT a name. `mzizi-conformity-check.ts` treats it as one\\n /// (`aria-label || role`), which makes its own rule unfireable for exactly the\\n /// `role=\\\"button\\\"` elements it was widened to cover; this is the correct\\n /// definition and the two now agree.\\n #[must_use]\\n pub fn accessible_name(&self) -> Option<&str> {\\n [\\n self.aria_label.as_deref(),\\n self.aria_labelledby.as_deref(),\\n self.text.as_deref(),\\n ]\\n .into_iter()\\n .flatten()\\n .map(str::trim)\\n .find(|name| !name.is_empty())\\n }\\n\\n /// The heading level, for `H1`-`H6`.\\n #[must_use]\\n pub fn heading_level(&self) -> Option<u8> {\\n let bytes = self.tag_name.as_bytes();\\n match bytes {\\n [h, d] if h.eq_ignore_ascii_case(&b'H') && d.is_ascii_digit() => {\\n let level = d - b'0';\\n (1..=6).contains(&level).then_some(level)\\n }\\n _ => None,\\n }\\n }\\n}\\n\\n/// One finding.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct A11yViolation {\\n /// Which rule.\\n pub rule: Rule,\\n /// How much it matters.\\n pub level: A11yLevel,\\n /// What is wrong, in words.\\n pub message: String,\\n /// The element's tag.\\n pub element: String,\\n /// A selector that finds it again.\\n pub selector: String,\\n /// Which helix node owns the component, when known.\\n pub node: Option<u32>,\\n /// The `data-slot` value, when present.\\n pub component_name: Option<String>,\\n /// Where the component's documentation is.\\n pub portal_url: Option<String>,\\n /// How to fix it.\\n pub fix: String,\\n}\\n\\n/// What a whole page scored.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct A11yAuditResult {\\n /// The URL audited.\\n pub url: String,\\n /// How many elements were observed.\\n pub total_elements: usize,\\n /// Every finding.\\n pub violations: Vec<A11yViolation>,\\n /// How many elements produced no finding.\\n pub passes: usize,\\n /// Percentage passing, 0-100.\\n pub score: u32,\\n}\\n\\n/// The touch-target floor the `.ts` uses, and the Apple HIG figure.\\n///\\n/// Material's is 48dp. This is a floor on the HIT AREA, not on the painted\\n/// control — see the module docs, defect 4.\\npub const DEFAULT_TOUCH_TARGET_MIN_PX: f64 = 44.0;\\n\\n/// What to check and how strictly.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct A11yConfig {\\n /// Which rules to run. Empty means none, which is not the same as [`None`].\\n pub rules: Option<BTreeSet<Rule>>,\\n /// The hit-area floor in CSS pixels.\\n pub touch_target_min_px: f64,\\n}\\n\\nimpl Default for A11yConfig {\\n fn default() -> Self {\\n Self {\\n rules: None,\\n touch_target_min_px: DEFAULT_TOUCH_TARGET_MIN_PX,\\n }\\n }\\n}\\n\\nimpl A11yConfig {\\n /// Whether a rule is enabled. Absent configuration means every rule.\\n #[must_use]\\n pub fn runs(&self, rule: Rule) -> bool {\\n self.rules.as_ref().is_none_or(|set| set.contains(&rule))\\n }\\n}\\n\\n/// Whether a string is usable bare after `#` in a selector.\\nfn is_plain_ident(value: &str) -> bool {\\n let mut chars = value.chars();\\n match chars.next() {\\n Some(first) if first.is_ascii_alphabetic() || first == '_' => {}\\n _ => return false,\\n }\\n chars.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')\\n}\\n\\n/// Escape a value for a double-quoted attribute selector.\\nfn escape_attr(value: &str) -> String {\\n value.replace('\\\\\\\\', \\\"\\\\\\\\\\\\\\\\\\\").replace('\\\"', \\\"\\\\\\\\\\\\\\\"\\\")\\n}\\n\\n/// A selector that finds this element again.\\n///\\n/// The `.ts` interpolates the id and the slot raw, so an id containing a quote,\\n/// a space or a leading digit produces a selector that does not parse — and the\\n/// selector's only job is to let a human or an agent locate the element. Quoted\\n/// attribute form is used whenever the bare form would not survive.\\n#[must_use]\\npub fn css_selector(node: &ObservedNode) -> String {\\n if let Some(id) = node.id.as_deref().filter(|s| !s.is_empty()) {\\n return if is_plain_ident(id) {\\n format!(\\\"#{id}\\\")\\n } else {\\n format!(\\\"[id=\\\\\\\"{}\\\\\\\"]\\\", escape_attr(id))\\n };\\n }\\n if let Some(slot) = node.data_slot.as_deref().filter(|s| !s.is_empty()) {\\n return format!(\\\"[data-slot=\\\\\\\"{}\\\\\\\"]\\\", escape_attr(slot));\\n }\\n let tag = node.tag_name.to_ascii_lowercase();\\n match node.first_class.as_deref().filter(|s| !s.is_empty()) {\\n Some(class) if is_plain_ident(class) => format!(\\\"{tag}.{class}\\\"),\\n Some(class) => format!(\\\"{tag}[class~=\\\\\\\"{}\\\\\\\"]\\\", escape_attr(class)),\\n None => tag,\\n }\\n}\\n\\n/// The heuristic the `.ts` uses to guess a node from a slot name.\\n///\\n/// It can only ever answer 2, 3 or 6, so it is a guess and named like one. Prefer\\n/// [`resolve_node`], which asks the registry first.\\n#[must_use]\\npub fn guess_node_from_slot(slot: &str) -> Option<u32> {\\n if slot.is_empty() {\\n return None;\\n }\\n if (slot.starts_with(\\\"mzizi-\\\") || slot.starts_with(\\\"nyuchi-\\\")) && !slot.contains(\\\"page\\\") {\\n return Some(3);\\n }\\n if slot.contains(\\\"page\\\") || slot.contains(\\\"layout\\\") {\\n return Some(6);\\n }\\n Some(2)\\n}\\n\\n/// Which node owns the component in this slot.\\n///\\n/// The registry lookup wins; the heuristic is the fallback for a consumer\\n/// auditing their own app, who has no Mzizi registry to consult.\\n#[must_use]\\npub fn resolve_node(slot: Option<&str>, registry: &BTreeMap<String, u32>) -> Option<u32> {\\n let slot = slot?;\\n registry\\n .get(slot)\\n .copied()\\n .or_else(|| guess_node_from_slot(slot))\\n}\\n\\n/// Build a violation carrying the element's identity.\\nfn violation(\\n node: &ObservedNode,\\n helix_node: Option<u32>,\\n rule: Rule,\\n level: A11yLevel,\\n message: String,\\n fix: &str,\\n) -> A11yViolation {\\n A11yViolation {\\n rule,\\n level,\\n message,\\n element: node.tag_name.clone(),\\n selector: css_selector(node),\\n node: helix_node,\\n component_name: node.data_slot.clone().filter(|s| !s.is_empty()),\\n portal_url: node.data_portal.clone().filter(|s| !s.is_empty()),\\n fix: fix.to_owned(),\\n }\\n}\\n\\n/// Check one element against every enabled rule.\\n///\\n/// `previous_heading` is the level of the last heading seen before this element,\\n/// which is why the caller iterates in document order. Returns every violation,\\n/// never just the first.\\n#[must_use]\\npub fn check_node(\\n node: &ObservedNode,\\n previous_heading: Option<u8>,\\n config: &A11yConfig,\\n registry: &BTreeMap<String, u32>,\\n) -> Vec<A11yViolation> {\\n let mut found = Vec::new();\\n let helix_node = resolve_node(node.data_slot.as_deref(), registry);\\n\\n if config.runs(Rule::ImgAlt) && node.tag_name.eq_ignore_ascii_case(\\\"img\\\") && node.alt.is_none()\\n {\\n found.push(violation(\\n node,\\n helix_node,\\n Rule::ImgAlt,\\n A11yLevel::Critical,\\n \\\"Image missing alt text\\\".to_owned(),\\n \\\"Add alt=\\\\\\\"\\\\\\\" for decorative or alt=\\\\\\\"description\\\\\\\" for informative\\\",\\n ));\\n }\\n\\n if config.runs(Rule::ButtonName) && node.is_button() && node.accessible_name().is_none() {\\n found.push(violation(\\n node,\\n helix_node,\\n Rule::ButtonName,\\n A11yLevel::Critical,\\n \\\"Button missing accessible name\\\".to_owned(),\\n \\\"Add aria-label, aria-labelledby, or visible text\\\",\\n ));\\n }\\n\\n if config.runs(Rule::TouchTarget)\\n && node.is_touch_target()\\n && node.visible\\n && let Some(hit) = node.hit_box\\n && (hit.height < config.touch_target_min_px || hit.width < config.touch_target_min_px)\\n {\\n let min = config.touch_target_min_px.round();\\n found.push(violation(\\n node,\\n helix_node,\\n Rule::TouchTarget,\\n A11yLevel::Moderate,\\n format!(\\n \\\"Touch target too small ({}×{}px, min {min}×{min})\\\",\\n hit.width.round(),\\n hit.height.round()\\n ),\\n \\\"Extend the interactive area with padding or surrounding spacing\\\",\\n ));\\n }\\n\\n // A heading may not skip a level going DOWN the document — h2 after h4 is a\\n // section ending, not a gap. The first heading on a page is compared against\\n // level 0, so an h3 with no h1 or h2 above it is a skip.\\n if config.runs(Rule::HeadingOrder)\\n && let Some(level) = node.heading_level()\\n {\\n let previous = previous_heading.unwrap_or(0);\\n if level > previous + 1 {\\n found.push(violation(\\n node,\\n helix_node,\\n Rule::HeadingOrder,\\n A11yLevel::Moderate,\\n format!(\\\"Heading level skipped (h{previous} → h{level})\\\"),\\n \\\"Use the next heading level down, or restructure the section\\\",\\n ));\\n }\\n }\\n\\n found\\n}\\n\\n/// Audit a page.\\n///\\n/// `nodes` must be in document order — [`Rule::HeadingOrder`] depends on it.\\n///\\n/// An element PASSES when it produced no violation. The `.ts` counts passes in\\n/// branches an interactive element cannot reach, so a correct button scored as\\n/// neither a pass nor a violation and the page score fell as accessibility rose.\\n#[must_use]\\npub fn audit(\\n url: impl Into<String>,\\n nodes: &[ObservedNode],\\n config: &A11yConfig,\\n registry: &BTreeMap<String, u32>,\\n) -> A11yAuditResult {\\n let mut violations = Vec::new();\\n let mut passes = 0usize;\\n let mut previous_heading: Option<u8> = None;\\n\\n for node in nodes {\\n let found = check_node(node, previous_heading, config, registry);\\n if found.is_empty() {\\n passes += 1;\\n }\\n violations.extend(found);\\n if let Some(level) = node.heading_level() {\\n previous_heading = Some(level);\\n }\\n }\\n\\n let total = nodes.len();\\n A11yAuditResult {\\n url: url.into(),\\n total_elements: total,\\n violations,\\n passes,\\n // A page with no elements is not failing anything, which is different from\\n // being untested — `total_elements` says which.\\n score: if total == 0 {\\n 100\\n } else {\\n ((passes as f64 / total as f64) * 100.0).round() as u32\\n },\\n }\\n}\\n\\n/// The worst level present, for a single go/no-go answer.\\n#[must_use]\\npub fn worst_level(violations: &[A11yViolation]) -> Option<A11yLevel> {\\n violations.iter().map(|v| v.level).max()\\n}\\n\\n/// Whether a result clears a score threshold.\\n#[must_use]\\npub fn meets_threshold(result: &A11yAuditResult, threshold: u32) -> bool {\\n result.score >= threshold\\n}\\n\"}],\"contract\":null}",
|
|
1197
|
+
"mzizi-ai-context": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-ai-context\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"AI context provider for N10. Generates context windows for AI assistants (Claude, Cursor, Copilot, Windsurf) with current ecosystem model, node architecture, and coding rules. No hardcoded counts — always accepts live values from get_system_counts(). Used by the MCP server and IDE integrations.\",\"crate\":{\"name\":\"mzizi-docs\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n10-documentation/mzizi-ai-context.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N10 documentation — the context window an AI assistant is given.\\n//!\\n//! The Rust implementation of `mzizi-ai-context`. Pure string assembly, no\\n//! framework, exactly as the `.ts` describes itself.\\n//!\\n//! # The defect this port had to confront\\n//!\\n//! The `.ts` opens with, in capitals: \\\"This lib never hardcodes counts. Counts\\n//! drift as the ecosystem evolves.\\\" It then hardcodes the entire node set as a\\n//! literal string — a ten-row table, N1 through N10, with a four-axis model above\\n//! it. Both are stale:\\n//!\\n//! * The node set is **uncapped** (§9) and runs past N10. N11 is the discovery\\n//! rung and N12 the skills rung; neither appears. An assistant handed this\\n//! context is told, in a document whose whole purpose is to be believed, that\\n//! N10 is the end.\\n//! * The horizontal / vertical / depth / outlier axis model is the retired\\n//! layer-and-axis scheme. `mzizi-seo`'s own header records being moved off\\n//! N6 \\\"because the layer/axis model is retired in favour of the DNA double\\n//! helix\\\".\\n//!\\n//! A count is not special. A hardcoded node LIST drifts the same way a hardcoded\\n//! node COUNT does, and this file proves it: the count rule was followed and the\\n//! list beside it went stale anyway.\\n//!\\n//! So the node map here is **data**, not a literal. [`NodeEntry`] rows are passed\\n//! in, the same way counts are, and [`current_nodes`] is a documented default for\\n//! a caller with nothing better — not a truth buried in a string. A caller that\\n//! reads `/api/v1/architecture` gets a map that cannot go stale at all.\\n//!\\n//! # Preserved rather than \\\"fixed\\\"\\n//!\\n//! The ten rules are reproduced as written, including rule 8's \\\"48px minimum\\\".\\n//! `scripts/validate-registry.mjs` records at length that the shipped primitives\\n//! never honoured the 56px/48px scale and that density won — but the rules block\\n//! is doctrine text owned by N10's authors, and silently editing what the system\\n//! tells an assistant about itself is not a porting decision. Flagged here, and\\n//! left for whoever owns §8.2 to settle.\\n//!\\n//! Rule 9's \\\"Query get_node_counts()\\\" and the Supabase/MCP footer are likewise\\n//! reproduced. They name a specific database and endpoint, which is exactly the\\n//! kind of fact that rots — see [`Endpoints`] for how a caller overrides them.\\n\\n/// Live ecosystem totals, supplied by the caller.\\n///\\n/// Optional in the `.ts` and optional here: an assistant is better told where to\\n/// find the numbers than given numbers that have drifted.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub struct EcosystemCounts {\\n /// Every component in the registry.\\n pub total_components: usize,\\n /// Those marked stable.\\n pub total_stable: usize,\\n /// How many nodes exist. Uncapped — see §9.\\n pub total_nodes: usize,\\n}\\n\\n/// One row of the node map.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct NodeEntry {\\n /// Node number. No upper bound.\\n pub number: u16,\\n /// Short name, e.g. `Primitives`.\\n pub label: String,\\n /// What the node is for, one line.\\n pub role: String,\\n}\\n\\nimpl NodeEntry {\\n /// Build a row.\\n #[must_use]\\n pub fn new(number: u16, label: &str, role: &str) -> Self {\\n Self {\\n number,\\n label: label.to_owned(),\\n role: role.to_owned(),\\n }\\n }\\n}\\n\\n/// The node set as it stands, for a caller with no live source.\\n///\\n/// A DEFAULT, not a definition. The authority is `/api/v1/architecture`, and a\\n/// caller that can reach it should pass those rows instead — that is the whole\\n/// point of the node map being a parameter. Kept here so the component still\\n/// produces something useful offline, and so the staleness is visible in a list\\n/// rather than buried in a prose table.\\n#[must_use]\\npub fn current_nodes() -> Vec<NodeEntry> {\\n vec![\\n NodeEntry::new(\\n 1,\\n \\\"Tokens\\\",\\n \\\"CSS substrate. The only node that defines CSS values.\\\",\\n ),\\n NodeEntry::new(2, \\\"Primitives\\\", \\\"Headless and accessible. Radix + CVA.\\\"),\\n NodeEntry::new(3, \\\"Brand\\\", \\\"N2 plus Ubuntu. Uses mzizi-harness.\\\"),\\n NodeEntry::new(4, \\\"Safety\\\", \\\"Gates. Validates input, guards AI output.\\\"),\\n NodeEntry::new(\\n 5,\\n \\\"Resilience\\\",\\n \\\"Circuit breakers, retries, fallback chains.\\\",\\n ),\\n NodeEntry::new(6, \\\"Pages\\\", \\\"Pure composition. No inline primitives.\\\"),\\n NodeEntry::new(7, \\\"Shell\\\", \\\"App chrome. Header, nav, theme, lifecycle.\\\"),\\n NodeEntry::new(\\n 8,\\n \\\"Assurance\\\",\\n \\\"Observability, SLO tracking, chaos testing.\\\",\\n ),\\n NodeEntry::new(\\n 9,\\n \\\"Fundi\\\",\\n \\\"Self-healing. Turns assurance signals into filed defects.\\\",\\n ),\\n NodeEntry::new(\\n 10,\\n \\\"Documentation\\\",\\n \\\"Self-describing. The system explains itself.\\\",\\n ),\\n NodeEntry::new(\\n 11,\\n \\\"Discovery\\\",\\n \\\"Machine visibility. Metadata and structured data.\\\",\\n ),\\n NodeEntry::new(\\n 12,\\n \\\"Skills\\\",\\n \\\"What the system knows how to do, authored in git.\\\",\\n ),\\n ]\\n}\\n\\n/// Where the machine-readable truth lives.\\n///\\n/// Parameters rather than literals because the `.ts` hardcoded a Supabase project\\n/// ref, an MCP hostname and the repo slug `nyuchi/design-portal` — and the repo is\\n/// `nyuchi/mzizi`. An assistant handed a wrong repo slug will look in the wrong\\n/// place and report that the code does not exist.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct Endpoints {\\n /// Human-facing site.\\n pub site: String,\\n /// `owner/repo` on GitHub.\\n pub repo: String,\\n /// MCP endpoint an assistant can call.\\n pub mcp: String,\\n}\\n\\nimpl Default for Endpoints {\\n fn default() -> Self {\\n Self {\\n site: \\\"https://mzizi.dev\\\".to_owned(),\\n repo: \\\"nyuchi/mzizi\\\".to_owned(),\\n mcp: \\\"https://mzizi.dev/api/v1/mcp\\\".to_owned(),\\n }\\n }\\n}\\n\\n/// Which sections to include.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct AiContextOptions {\\n /// Live totals, if the caller has them.\\n pub counts: Option<EcosystemCounts>,\\n /// The ten rules.\\n pub include_rules: bool,\\n /// The heading and source line.\\n pub include_architecture: bool,\\n /// The node map table.\\n pub include_node_map: bool,\\n /// Node rows. Empty means \\\"use [`current_nodes`]\\\".\\n pub nodes: Vec<NodeEntry>,\\n /// Where the truth lives.\\n pub endpoints: Endpoints,\\n}\\n\\nimpl Default for AiContextOptions {\\n /// Matches the `.ts` defaults: all three sections on.\\n fn default() -> Self {\\n Self {\\n counts: None,\\n include_rules: true,\\n include_architecture: true,\\n include_node_map: true,\\n nodes: Vec::new(),\\n endpoints: Endpoints::default(),\\n }\\n }\\n}\\n\\n/// The ten rules, reproduced verbatim from the `.ts`.\\n///\\n/// See the module docs on rule 8 — reproduced as written rather than silently\\n/// reconciled with what the primitives actually ship.\\nconst ECOSYSTEM_RULES: &str = \\\"\\\\\\n## Rules (non-negotiable)\\n\\n1. CSS values live only in N1. All other nodes use var(--token-name). Never hex outside N1.\\n2. Icons import from @/lib/icons only. Never from lucide-react directly.\\n3. N2 primitives never import useMziziHarness.\\n4. N3 brand components always destructure { log, motion, LiveRegion } from useMziziHarness.\\n5. N6 pages: pure composition, semantic CSS vars only, accept children/slots.\\n6. All interactive components: data-slot + data-portal attributes required.\\n7. Status colors use semantic tokens: --status-success, --status-error, --status-warning.\\n8. Touch targets: 48px minimum. Focus rings: focus-visible:outline-2.\\n9. No hardcoded numbers in code or copy. Query the live counts.\\n10. Shona terms come from the database. Never translate or invent them.\\\";\\n\\n/// Render the node map as a markdown table.\\nfn render_node_map(nodes: &[NodeEntry]) -> String {\\n // Width the first column to the widest label so the table stays readable at\\n // any node count — the `.ts` aligned its ten rows by hand, which is why an\\n // eleventh could not be added without redoing the whitespace.\\n let widest = nodes.iter().map(|n| n.label.len()).max().unwrap_or(0);\\n\\n let mut out = String::new();\\n out.push_str(\\\"## Node map\\\\n\\\\n\\\");\\n out.push_str(\\n \\\"Every component belongs to exactly one node. The node set is UNCAPPED — a\\\\n\\\\\\n new node may be added at any time, so never assume the highest number here\\\\n\\\\\\n is the last one.\\\\n\\\\n\\\",\\n );\\n for n in nodes {\\n out.push_str(&format!(\\n \\\" N{:<3} {:<width$} — {}\\\\n\\\",\\n n.number,\\n n.label,\\n n.role,\\n width = widest\\n ));\\n }\\n out\\n}\\n\\n/// Build the context window.\\n///\\n/// Mirrors the `.ts` `generateAIContext`, with the node map taken from\\n/// `options.nodes` rather than a literal.\\n#[must_use]\\npub fn generate_ai_context(options: &AiContextOptions) -> String {\\n let mut parts: Vec<String> = Vec::new();\\n\\n if options.include_architecture {\\n parts.push(\\\"# Mzizi Design System\\\".to_owned());\\n parts.push(String::new());\\n if let Some(c) = options.counts {\\n parts.push(format!(\\n \\\"Live counts: {} components total, {} stable, across {} nodes.\\\",\\n c.total_components, c.total_stable, c.total_nodes\\n ));\\n }\\n parts.push(format!(\\n \\\"Source: {} | GitHub: {}\\\",\\n options.endpoints.site, options.endpoints.repo\\n ));\\n if options.counts.is_none() {\\n parts.push(format!(\\\"For live counts: {}\\\", options.endpoints.mcp));\\n }\\n parts.push(String::new());\\n }\\n\\n if options.include_node_map {\\n let owned;\\n let nodes: &[NodeEntry] = if options.nodes.is_empty() {\\n owned = current_nodes();\\n &owned\\n } else {\\n &options.nodes\\n };\\n parts.push(render_node_map(nodes));\\n parts.push(String::new());\\n }\\n\\n if options.include_rules {\\n parts.push(ECOSYSTEM_RULES.to_owned());\\n parts.push(String::new());\\n }\\n\\n parts.push(format!(\\\"MCP server: {}\\\", options.endpoints.mcp));\\n\\n parts.join(\\\"\\\\n\\\")\\n}\\n\\n/// The four named surfaces from the `.ts` `aiContextPresets`.\\n///\\n/// `mcp` and `claude` were byte-identical in the `.ts`, as were `copilot` and\\n/// `cursor`. Kept as four names because callers reference them by name, with the\\n/// duplication stated rather than hidden.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum Preset {\\n /// Full context for the MCP server.\\n Mcp,\\n /// Rules and node map, no architecture prose.\\n Copilot,\\n /// Full context. Identical to [`Preset::Mcp`].\\n Claude,\\n /// Rules and node map. Identical to [`Preset::Copilot`].\\n Cursor,\\n}\\n\\nimpl Preset {\\n /// Options for this surface.\\n #[must_use]\\n pub fn options(self, counts: Option<EcosystemCounts>) -> AiContextOptions {\\n match self {\\n Self::Mcp | Self::Claude => AiContextOptions {\\n counts,\\n ..AiContextOptions::default()\\n },\\n Self::Copilot | Self::Cursor => AiContextOptions {\\n counts: None,\\n include_architecture: false,\\n ..AiContextOptions::default()\\n },\\n }\\n }\\n\\n /// Render this surface's context window.\\n #[must_use]\\n pub fn render(self, counts: Option<EcosystemCounts>) -> String {\\n generate_ai_context(&self.options(counts))\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn default_includes_all_three_sections() {\\n let s = generate_ai_context(&AiContextOptions::default());\\n assert!(s.contains(\\\"# Mzizi Design System\\\"));\\n assert!(s.contains(\\\"## Node map\\\"));\\n assert!(s.contains(\\\"## Rules (non-negotiable)\\\"));\\n }\\n\\n #[test]\\n fn the_node_map_reaches_past_n10() {\\n // The whole reason this file diverges: the .ts stops at N10.\\n let s = generate_ai_context(&AiContextOptions::default());\\n assert!(s.contains(\\\"N11\\\"), \\\"N11 discovery is missing\\\");\\n assert!(s.contains(\\\"N12\\\"), \\\"N12 skills is missing\\\");\\n }\\n\\n #[test]\\n fn it_says_the_node_set_is_uncapped() {\\n let s = generate_ai_context(&AiContextOptions::default());\\n assert!(s.contains(\\\"UNCAPPED\\\"));\\n }\\n\\n #[test]\\n fn the_retired_axis_model_is_gone() {\\n let s = generate_ai_context(&AiContextOptions::default());\\n for retired in [\\\"horizontal\\\", \\\"vertical\\\", \\\"outlier\\\", \\\"depth\\\"] {\\n assert!(\\n !s.contains(retired),\\n \\\"retired axis word {retired} survived the port\\\"\\n );\\n }\\n }\\n\\n #[test]\\n fn the_repo_slug_is_the_real_one() {\\n let s = generate_ai_context(&AiContextOptions::default());\\n assert!(s.contains(\\\"nyuchi/mzizi\\\"));\\n assert!(!s.contains(\\\"design-portal\\\"));\\n }\\n\\n #[test]\\n fn counts_appear_only_when_supplied() {\\n let without = generate_ai_context(&AiContextOptions::default());\\n assert!(!without.contains(\\\"Live counts\\\"));\\n assert!(without.contains(\\\"For live counts\\\"));\\n\\n let with = generate_ai_context(&AiContextOptions {\\n counts: Some(EcosystemCounts {\\n total_components: 575,\\n total_stable: 500,\\n total_nodes: 12,\\n }),\\n ..AiContextOptions::default()\\n });\\n assert!(with.contains(\\\"575 components total, 500 stable, across 12 nodes\\\"));\\n assert!(!with.contains(\\\"For live counts\\\"));\\n }\\n\\n #[test]\\n fn caller_supplied_nodes_replace_the_default() {\\n let s = generate_ai_context(&AiContextOptions {\\n nodes: vec![NodeEntry::new(\\n 42,\\n \\\"Future\\\",\\n \\\"A node nobody has invented yet.\\\",\\n )],\\n ..AiContextOptions::default()\\n });\\n assert!(s.contains(\\\"N42\\\"));\\n assert!(s.contains(\\\"A node nobody has invented yet.\\\"));\\n assert!(!s.contains(\\\"Primitives\\\"), \\\"the default map leaked through\\\");\\n }\\n\\n #[test]\\n fn the_table_aligns_at_any_node_count() {\\n // The .ts hand-aligned ten rows, so an eleventh could not be added\\n // without redoing the whitespace.\\n let s = render_node_map(&[\\n NodeEntry::new(1, \\\"A\\\", \\\"short\\\"),\\n NodeEntry::new(2, \\\"AVeryLongLabelIndeed\\\", \\\"long\\\"),\\n ]);\\n // Filter on the row prefix, not on \\\" — \\\": the intro paragraph above the\\n // rows contains an em dash too, and matching it compared a prose line\\n // against a table row.\\n let lines: Vec<&str> = s.lines().filter(|l| l.starts_with(\\\" N\\\")).collect();\\n assert_eq!(lines.len(), 2);\\n let col = |l: &str| l.find(\\\" — \\\").unwrap();\\n assert_eq!(col(lines[0]), col(lines[1]));\\n }\\n\\n #[test]\\n fn presets_that_the_ts_made_identical_stay_identical() {\\n assert_eq!(Preset::Mcp.render(None), Preset::Claude.render(None));\\n assert_eq!(Preset::Copilot.render(None), Preset::Cursor.render(None));\\n }\\n\\n #[test]\\n fn ide_presets_drop_the_architecture_prose_only() {\\n let s = Preset::Copilot.render(None);\\n assert!(!s.contains(\\\"# Mzizi Design System\\\"));\\n assert!(s.contains(\\\"## Node map\\\"));\\n assert!(s.contains(\\\"## Rules\\\"));\\n }\\n\\n #[test]\\n fn endpoints_are_overridable() {\\n let s = generate_ai_context(&AiContextOptions {\\n endpoints: Endpoints {\\n site: \\\"https://example.test\\\".to_owned(),\\n repo: \\\"acme/thing\\\".to_owned(),\\n mcp: \\\"https://example.test/mcp\\\".to_owned(),\\n },\\n ..AiContextOptions::default()\\n });\\n assert!(s.contains(\\\"acme/thing\\\"));\\n assert!(!s.contains(\\\"nyuchi/mzizi\\\"));\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1198
|
+
"mzizi-alert-banner": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-alert-banner\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Universal branded alert banner with mineral-coded severity levels. Used across all ecosystem apps for weather alerts, system notifications, security warnings, trust advisories, and any time-sensitive information. Severity maps to minerals: watch=cobalt, moderate=terracotta, severe=gold. Renders as a prominent banner with dismiss, detail action, and safety instructions.\",\"crate\":{\"name\":\"mzizi-brand\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n3-brand/mzizi-alert-banner.rs\",\"type\":\"registry:rust\",\"content\":\"//! MZIZI ALERT BANNER — N3 brand, Dioxus. A Mzizi Roots component.\\n//!\\n//! The Rust sibling of `mzizi-alert-banner.tsx`: the mineral-coded severity alert used for\\n//! weather, security, trust and system alerts across the ecosystem. Same `data-slot`, same\\n//! `role=\\\"alert\\\"`, same three severities with the same mineral and surface expressions.\\n//!\\n//! # Named for the registry item, not the `.tsx` export\\n//!\\n//! The `.tsx` exports this component as `MziziWeatherAlert`, a name from before it was\\n//! generalised. The registry item is `mzizi-alert-banner`, so the Rust component is\\n//! [`MziziAlertBanner`].\\n//!\\n//! # The details button meets the touch floor here\\n//!\\n//! The `.tsx`'s \\\"View Details\\\" button is `h-10` — 40px, under the 48px floor its own header\\n//! claims (`TOUCH 48px+`). This port renders it `min-h-[48px]`, and the contract asserts it.\\n//! **The `.tsx` sibling still ships `h-10`.**\\n\\nuse dioxus::prelude::*;\\n\\n/// The component's contract, in the clause grammar of the Mzizi language's `contract … end`\\n/// block (mzizi-dev/mzizi `design/RFC-0006-contracts.md`). `mzizi-brand`'s contract suite\\n/// evaluates every clause against this component's server-rendered markup and fails on any\\n/// clause it cannot evaluate.\\npub const CONTRACT: &str = r#\\\"contract\\n slot is \\\"mzizi-alert-banner\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-alert-banner\\\"\\n role is \\\"alert\\\"\\n every alert_severity label not_empty\\n watch.mineral uses \\\"--severity-cold\\\"\\n severe.mineral uses \\\"--severity-severe\\\"\\n button \\\"Dismiss\\\" min_height 48\\n button \\\"View Details\\\" min_height 48\\nend\\\"#;\\n\\n/// How serious the alert is. Matches `severityConfig` in the `.tsx`.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\\npub enum AlertSeverity {\\n /// Heads-up; cobalt surface.\\n Watch,\\n /// Take care; terracotta.\\n Moderate,\\n /// Act now; the severe mineral on a gold surface.\\n Severe,\\n}\\n\\nimpl AlertSeverity {\\n /// Every severity, for exhaustive checks.\\n pub const ALL: [Self; 3] = [Self::Watch, Self::Moderate, Self::Severe];\\n\\n /// The mineral: left border, label colour and details-button fill.\\n #[must_use]\\n pub const fn mineral(self) -> &'static str {\\n match self {\\n Self::Watch => \\\"var(--severity-cold, #3B82F6)\\\",\\n Self::Moderate => \\\"var(--severity-high,var(--color-terracotta,#D4A574))\\\",\\n Self::Severe => \\\"var(--severity-severe, #EF4444)\\\",\\n }\\n }\\n\\n /// The colour mixed at 8% into the card surface.\\n #[must_use]\\n pub const fn surface(self) -> &'static str {\\n match self {\\n Self::Watch => \\\"var(--color-cobalt,#00B0FF)\\\",\\n Self::Moderate => \\\"var(--color-terracotta,#D4A574)\\\",\\n Self::Severe => \\\"var(--color-gold,#FFD740)\\\",\\n }\\n }\\n\\n /// The leading glyph.\\n #[must_use]\\n pub const fn icon(self) -> &'static str {\\n match self {\\n Self::Watch => \\\"👁\\\",\\n Self::Moderate => \\\"⚠️\\\",\\n Self::Severe => \\\"🚨\\\",\\n }\\n }\\n\\n /// The uppercase label shown before the alert type.\\n #[must_use]\\n pub const fn label(self) -> &'static str {\\n match self {\\n Self::Watch => \\\"Watch\\\",\\n Self::Moderate => \\\"Moderate\\\",\\n Self::Severe => \\\"Severe\\\",\\n }\\n }\\n\\n /// The value the `.tsx` accepts for `severity`.\\n #[must_use]\\n pub const fn slug(self) -> &'static str {\\n match self {\\n Self::Watch => \\\"watch\\\",\\n Self::Moderate => \\\"moderate\\\",\\n Self::Severe => \\\"severe\\\",\\n }\\n }\\n}\\n\\n/// The validity line, matching the `.tsx`'s two optional template fragments.\\n#[must_use]\\npub fn validity_line(valid_from: Option<&str>, valid_until: Option<&str>) -> Option<String> {\\n match (valid_from, valid_until) {\\n (None, None) => None,\\n (Some(from), None) => Some(format!(\\\"From {from}\\\")),\\n (None, Some(until)) => Some(format!(\\\" until {until}\\\")),\\n (Some(from), Some(until)) => Some(format!(\\\"From {from} until {until}\\\")),\\n }\\n}\\n\\n/// Join a base class string with a consumer's extra classes, without a trailing space.\\nfn join(base: &str, extra: &str) -> String {\\n if extra.is_empty() {\\n base.to_owned()\\n } else {\\n format!(\\\"{base} {extra}\\\")\\n }\\n}\\n\\nconst DISMISS: &str = \\\"min-h-[48px] text-sm text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary,#00B0FF)]\\\";\\nconst DETAILS: &str =\\n \\\"min-h-[48px] rounded-full px-4 text-[12px] font-medium transition-opacity hover:opacity-80\\\";\\n\\n/// Props for [`MziziAlertBanner`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziAlertBannerProps {\\n /// Alert type, shown after the severity label (e.g. \\\"Thunderstorm\\\").\\n #[props(into)]\\n pub r#type: String,\\n /// How serious the alert is.\\n pub severity: AlertSeverity,\\n /// The headline.\\n #[props(into)]\\n pub headline: String,\\n /// Supporting description.\\n #[props(default)]\\n pub description: Option<String>,\\n /// Affected areas, joined with commas.\\n #[props(default)]\\n pub areas: Vec<String>,\\n /// Start of validity, as the host formats it.\\n #[props(default)]\\n pub valid_from: Option<String>,\\n /// End of validity, as the host formats it.\\n #[props(default)]\\n pub valid_until: Option<String>,\\n /// What to do.\\n #[props(default)]\\n pub instructions: Option<String>,\\n /// Shows a dismiss control when set.\\n #[props(default)]\\n pub on_dismiss: Option<EventHandler<()>>,\\n /// Shows a \\\"View Details\\\" button when set.\\n #[props(default)]\\n pub on_details: Option<EventHandler<()>>,\\n /// Extra classes, appended last.\\n #[props(default)]\\n pub class: String,\\n}\\n\\n/// A mineral-coded severity alert.\\n#[component]\\npub fn MziziAlertBanner(props: MziziAlertBannerProps) -> Element {\\n let severity = props.severity;\\n let mineral = severity.mineral();\\n let surface = severity.surface();\\n let validity = validity_line(props.valid_from.as_deref(), props.valid_until.as_deref());\\n let areas = props.areas.join(\\\", \\\");\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-alert-banner\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-alert-banner\\\",\\n \\\"data-severity\\\": severity.slug(),\\n role: \\\"alert\\\",\\n class: join(\\\"space-y-2 rounded-[var(--radius-lg,14px)] border-l-[3px] p-4\\\", &props.class),\\n style: \\\"border-left-color: {mineral}; background-color: color-mix(in srgb, {surface} 8%, var(--card));\\\",\\n div { class: \\\"flex items-start justify-between gap-2\\\",\\n div { class: \\\"flex items-center gap-2\\\",\\n span { {severity.icon()} }\\n span { class: \\\"text-xs font-bold tracking-wider uppercase\\\", style: \\\"color: {mineral};\\\",\\n \\\"{severity.label()} — {props.r#type}\\\"\\n }\\n }\\n if let Some(on_dismiss) = props.on_dismiss {\\n button {\\n r#type: \\\"button\\\",\\n class: DISMISS,\\n \\\"aria-label\\\": \\\"Dismiss\\\",\\n onclick: move |_| on_dismiss.call(()),\\n \\\"✕\\\"\\n }\\n }\\n }\\n p { class: \\\"text-sm font-semibold text-foreground\\\", style: \\\"font-family: var(--font-serif);\\\",\\n \\\"{props.headline}\\\"\\n }\\n if let Some(description) = &props.description {\\n p { class: \\\"text-xs leading-relaxed text-muted-foreground\\\", \\\"{description}\\\" }\\n }\\n if !props.areas.is_empty() {\\n p { class: \\\"text-[11px] text-muted-foreground\\\", \\\"Areas: {areas}\\\" }\\n }\\n if let Some(validity) = validity {\\n p { class: \\\"text-[10px] text-muted-foreground\\\", \\\"{validity}\\\" }\\n }\\n if let Some(instructions) = &props.instructions {\\n div { class: \\\"rounded-[var(--radius-sm,7px)] bg-muted p-2.5\\\",\\n p { class: \\\"text-xs leading-relaxed text-foreground\\\", \\\"⚡ {instructions}\\\" }\\n }\\n }\\n if let Some(on_details) = props.on_details {\\n button {\\n r#type: \\\"button\\\",\\n class: DETAILS,\\n style: \\\"background-color: {mineral}; color: var(--foreground, #0A0A0A);\\\",\\n onclick: move |_| on_details.call(()),\\n \\\"View Details\\\"\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn validity_matches_the_two_template_fragments() {\\n assert_eq!(validity_line(None, None), None);\\n assert_eq!(\\n validity_line(Some(\\\"06:00\\\"), None).as_deref(),\\n Some(\\\"From 06:00\\\")\\n );\\n assert_eq!(\\n validity_line(None, Some(\\\"18:00\\\")).as_deref(),\\n Some(\\\" until 18:00\\\")\\n );\\n assert_eq!(\\n validity_line(Some(\\\"06:00\\\"), Some(\\\"18:00\\\")).as_deref(),\\n Some(\\\"From 06:00 until 18:00\\\")\\n );\\n }\\n\\n #[test]\\n fn every_severity_has_a_distinct_mineral() {\\n let minerals: Vec<_> = AlertSeverity::ALL.iter().map(|s| s.mineral()).collect();\\n assert_eq!(minerals.len(), 3);\\n assert_ne!(minerals[0], minerals[1]);\\n assert_ne!(minerals[1], minerals[2]);\\n }\\n}\\n\"}],\"contract\":\"contract\\n slot is \\\"mzizi-alert-banner\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-alert-banner\\\"\\n role is \\\"alert\\\"\\n every alert_severity label not_empty\\n watch.mineral uses \\\"--severity-cold\\\"\\n severe.mineral uses \\\"--severity-severe\\\"\\n button \\\"Dismiss\\\" min_height 48\\n button \\\"View Details\\\" min_height 48\\nend\"}",
|
|
1199
|
+
"mzizi-alert-engine": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-alert-engine\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"SLO/SLA tracking and threshold-based alerting with escalation. Defines budgets (error budget, latency budget), monitors them, and fires alerts when thresholds are breached. Supports severity escalation: warn → page → incident. Competitors: PagerDuty, Grafana Alerting.\",\"crate\":{\"name\":\"mzizi-assurance\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n8-assurance/mzizi-alert-engine.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N8 assurance — SLO tracking, burn-rate alerting and escalation.\\n//!\\n//! The Rust implementation of `mzizi-alert-engine`.\\n//!\\n//! # What this owns\\n//!\\n//! Burn-rate arithmetic, the escalation decision, and the alert lifecycle. Not\\n//! the timer, not the metrics store, not the DOM. The `.ts` sibling's `evaluate`\\n//! runs on a `setInterval` and reads `const currentValue = 99.95` — a literal,\\n//! under a comment saying *\\\"in production this would query metrics… here we\\n//! define the contract.\\\"*\\n//!\\n//! So the measurement is the host's and always was. What belongs here is the part\\n//! that must not vary: given an observed value and an SLO, what fires and how\\n//! loudly.\\n//!\\n//! # Two defects in the TypeScript, fixed here rather than ported\\n//!\\n//! **1. One breach fired one alert per matching escalation tier.** The `.ts`\\n//! loops every tier and fires whenever `burnRate >= esc.burnRate`, so an SLO\\n//! escalating at 1× warning / 2× critical / 5× page produces *three* alerts at a\\n//! burn rate of 6 — and one of them pages a human, three times, for a single\\n//! breach. [`escalate_to`] returns the **highest matching tier, once**, which is\\n//! what an escalation ladder means.\\n//!\\n//! **2. Alert ids collided.** `alert-${Date.now()}` is unique only if no two\\n//! alerts fire in the same millisecond, and a burn-rate breach fires several\\n//! alerts in the same tick by construction — so the defects compounded, with the\\n//! duplicates overwriting each other in the `Map`. Ids are supplied here, for the\\n//! same reason trace ids are: a core has no clock and should not invent identity.\\n//!\\n//! Neither is a translation choice. A faithful port would have carried both\\n//! across, and `cargo check` would have been perfectly happy with them.\\n\\nuse std::collections::BTreeMap;\\n\\n/// How loud an alert is.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum AlertSeverity {\\n /// Informational. No action expected.\\n Info,\\n /// Someone should look when convenient.\\n Warning,\\n /// Someone should look now.\\n Critical,\\n /// Wake a human.\\n Page,\\n}\\n\\nimpl AlertSeverity {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Info => \\\"info\\\",\\n Self::Warning => \\\"warning\\\",\\n Self::Critical => \\\"critical\\\",\\n Self::Page => \\\"page\\\",\\n }\\n }\\n}\\n\\n/// Where an alert is in its life.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum AlertState {\\n /// Currently breaching.\\n Firing,\\n /// Breaching but inside its grace period.\\n Pending,\\n /// No longer breaching.\\n Resolved,\\n}\\n\\nimpl AlertState {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Firing => \\\"firing\\\",\\n Self::Pending => \\\"pending\\\",\\n Self::Resolved => \\\"resolved\\\",\\n }\\n }\\n}\\n\\n/// Which metric an SLO tracks.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum SloMetric {\\n /// Fraction of requests served.\\n Availability,\\n /// 99th-percentile latency.\\n LatencyP99,\\n /// Fraction of requests that errored.\\n ErrorRate,\\n /// Fraction of requests that succeeded.\\n SuccessRate,\\n}\\n\\nimpl SloMetric {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Availability => \\\"availability\\\",\\n Self::LatencyP99 => \\\"latency_p99\\\",\\n Self::ErrorRate => \\\"error_rate\\\",\\n Self::SuccessRate => \\\"success_rate\\\",\\n }\\n }\\n}\\n\\n/// One rung of an escalation ladder.\\n#[derive(Debug, Clone, Copy, PartialEq)]\\npub struct Escalation {\\n /// Burn rate at or above which this rung applies.\\n pub burn_rate: f64,\\n /// How loud to be at this rung.\\n pub severity: AlertSeverity,\\n}\\n\\n/// A service-level objective.\\n#[derive(Debug, Clone)]\\npub struct SloDefinition {\\n /// Stable id.\\n pub id: String,\\n /// Human name.\\n pub name: String,\\n /// Target percentage, e.g. `99.9`.\\n pub target: f64,\\n /// Window in hours, e.g. `720` for 30 days.\\n pub window_hours: u32,\\n /// Which metric this tracks.\\n pub metric: SloMetric,\\n /// Which mini-apps it covers.\\n pub mini_apps: Vec<String>,\\n /// The escalation ladder. Order does not matter — the highest match wins.\\n pub escalation: Vec<Escalation>,\\n}\\n\\n/// A fired alert.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct Alert {\\n /// Stable id, supplied by the host.\\n pub id: String,\\n /// The SLO this came from, when it came from one.\\n pub slo_id: Option<String>,\\n /// Human name.\\n pub name: String,\\n /// How loud.\\n pub severity: AlertSeverity,\\n /// Where it is in its life.\\n pub state: AlertState,\\n /// What happened, in words.\\n pub message: String,\\n /// When it fired, in milliseconds since the Unix epoch.\\n pub fired_at_ms: f64,\\n /// When it resolved.\\n pub resolved_at_ms: Option<f64>,\\n /// Components implicated.\\n ///\\n /// Supplied by the host. The `.ts` falls back to\\n /// `document.querySelectorAll(\\\"[data-portal]\\\")`, which is DOM access — the\\n /// host's business, and meaningless in a Worker or a native shell.\\n pub affected_components: Vec<String>,\\n /// Mini-apps implicated.\\n pub affected_mini_apps: Vec<String>,\\n /// Who has been told.\\n pub notified: Vec<String>,\\n /// Where the runbook is.\\n pub runbook_url: Option<String>,\\n}\\n\\n/// Error-budget burn rate for an observed value against a target.\\n///\\n/// Returns `0.0` while the objective is being met, and otherwise how many times\\n/// over the budget the breach is: a target of 99.9 observed at 99.8 has consumed\\n/// its whole 0.1 budget once, so the rate is 1.0.\\n///\\n/// # A 100% target\\n///\\n/// `100 - target` is the budget, and a target of 100 leaves none — so any breach\\n/// is infinitely over it. The `.ts` divides straight through and yields\\n/// `Infinity`, which then compares `>=` true against every escalation rung and\\n/// pages immediately. That is arguably the right answer for an SLO that admits no\\n/// failure, but it arrives by accident. Here it is explicit: an unmeetable\\n/// objective returns [`f64::INFINITY`] deliberately, and a caller can decide\\n/// whether that objective was a mistake.\\n#[must_use]\\npub fn burn_rate(observed: f64, target: f64) -> f64 {\\n let remaining = observed - target;\\n if remaining >= 0.0 {\\n return 0.0;\\n }\\n let budget = 100.0 - target;\\n if budget <= 0.0 {\\n return f64::INFINITY;\\n }\\n remaining.abs() / budget\\n}\\n\\n/// The single rung an escalation ladder reaches at this burn rate.\\n///\\n/// Returns the **highest** matching rung, or `None` when none match. This is the\\n/// fix for the `.ts`'s per-rung firing: an escalation ladder describes one\\n/// response that gets louder, not a set of independent triggers, and treating it\\n/// as the latter pages a human once per rung they configured.\\n///\\n/// Ties on `burn_rate` break toward the louder severity, because under-alerting a\\n/// breach is the worse error.\\n#[must_use]\\npub fn escalate_to(escalation: &[Escalation], burn_rate: f64) -> Option<Escalation> {\\n escalation\\n .iter()\\n .filter(|e| burn_rate >= e.burn_rate)\\n .copied()\\n .max_by(|a, b| {\\n a.burn_rate\\n .partial_cmp(&b.burn_rate)\\n .unwrap_or(std::cmp::Ordering::Equal)\\n .then(a.severity.cmp(&b.severity))\\n })\\n}\\n\\n/// Evaluate one SLO against an observed value.\\n///\\n/// Returns at most ONE alert, which is the whole point. `id` and `fired_at_ms`\\n/// are supplied because a core has neither a counter nor a clock.\\n#[must_use]\\npub fn evaluate_slo(\\n slo: &SloDefinition,\\n observed: f64,\\n id: impl Into<String>,\\n fired_at_ms: f64,\\n) -> Option<Alert> {\\n let rate = burn_rate(observed, slo.target);\\n let rung = escalate_to(&slo.escalation, rate)?;\\n Some(Alert {\\n id: id.into(),\\n slo_id: Some(slo.id.clone()),\\n name: format!(\\\"SLO breach: {}\\\", slo.name),\\n severity: rung.severity,\\n state: AlertState::Firing,\\n message: format!(\\n \\\"{} burn rate {rate:.2}x (threshold: {}x). Current: {observed}%, target: {}%\\\",\\n slo.name, rung.burn_rate, slo.target\\n ),\\n fired_at_ms,\\n resolved_at_ms: None,\\n affected_components: Vec::new(),\\n affected_mini_apps: slo.mini_apps.clone(),\\n notified: Vec::new(),\\n runbook_url: Some(format!(\\\"https://mzizi.dev/runbooks/{}\\\", slo.id)),\\n })\\n}\\n\\n/// The set of alerts a host is tracking.\\n///\\n/// Ordered, so \\\"the active alerts\\\" is a stable list rather than whatever order a\\n/// hash map happened to produce — a dashboard that reshuffles between polls is\\n/// one nobody trusts.\\n#[derive(Debug, Clone, Default)]\\npub struct AlertLog {\\n alerts: BTreeMap<String, Alert>,\\n}\\n\\nimpl AlertLog {\\n /// An empty log.\\n #[must_use]\\n pub fn new() -> Self {\\n Self::default()\\n }\\n\\n /// Record a fired alert, returning the previous one with that id if any.\\n ///\\n /// Returning the displaced alert rather than dropping it is deliberate: with\\n /// the `.ts`'s colliding `Date.now()` ids this silently destroyed alerts, and\\n /// a caller that ignores the return value at least had the chance not to.\\n pub fn fire(&mut self, alert: Alert) -> Option<Alert> {\\n self.alerts.insert(alert.id.clone(), alert)\\n }\\n\\n /// Mark an alert resolved. Returns false if the id is unknown.\\n pub fn resolve(&mut self, id: &str, resolved_at_ms: f64) -> bool {\\n match self.alerts.get_mut(id) {\\n Some(alert) => {\\n alert.state = AlertState::Resolved;\\n alert.resolved_at_ms = Some(resolved_at_ms);\\n true\\n }\\n None => false,\\n }\\n }\\n\\n /// Every alert currently firing.\\n #[must_use]\\n pub fn active(&self) -> Vec<&Alert> {\\n self.alerts\\n .values()\\n .filter(|a| a.state == AlertState::Firing)\\n .collect()\\n }\\n\\n /// Every alert, resolved or not.\\n #[must_use]\\n pub fn all(&self) -> Vec<&Alert> {\\n self.alerts.values().collect()\\n }\\n\\n /// How many alerts are held.\\n #[must_use]\\n pub fn len(&self) -> usize {\\n self.alerts.len()\\n }\\n\\n /// Whether the log is empty.\\n #[must_use]\\n pub fn is_empty(&self) -> bool {\\n self.alerts.is_empty()\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1200
|
+
"mzizi-api-probe": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-api-probe\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"API endpoint health checker. Pings all Mzizi registry API endpoints (from component_backlinks) and reports status codes, latency, and error rates. Feeds into mzizi-platform-health. Uses the backlinks system built in v4.8.0.\",\"crate\":{\"name\":\"mzizi-assurance\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n8-assurance/mzizi-api-probe.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N8 assurance — endpoint health classification.\\n//!\\n//! The Rust implementation of `mzizi-api-probe`.\\n//!\\n//! # What this owns\\n//!\\n//! What a response *means*: healthy, degraded, down or timed out. Not the\\n//! request. The `.ts` sibling's `runApiProbe` performs `fetch` with an\\n//! `AbortController`, which is I/O and therefore the host's — the same boundary\\n//! the exporter draws at sending and the probe draws at driving a browser.\\n//!\\n//! # Two defects in the TypeScript, fixed here\\n//!\\n//! **1. A timeout was detected by string-matching the error.** The `.ts` writes\\n//! `String(err).includes(\\\"abort\\\")`, and the text of an abort differs by runtime:\\n//! *\\\"The operation was aborted\\\"*, *\\\"This operation was aborted\\\"*, *\\\"signal is\\n//! aborted without reason\\\"*. So the same timeout classifies as `timeout` in one\\n//! host and `down` in another — and `down` pages people while `timeout` often\\n//! does not. The caller always knows whether it aborted, so [`Outcome`] carries\\n//! the fact instead of the core guessing at prose.\\n//!\\n//! **2. The degraded threshold ignored the timeout.** 2000 ms was hardcoded while\\n//! the request timeout was configurable at 5000. Set the timeout below 2000 and\\n//! nothing can ever be `Degraded`: the request aborts before it can be slow. The\\n//! threshold is a parameter here, and [`degraded_threshold_for`] derives a\\n//! sensible one from the timeout so the two cannot contradict each other.\\n//!\\n//! # One field that never had a producer\\n//!\\n//! `EndpointStatus` declares `\\\"unknown\\\"` and nothing in the `.ts` ever returns\\n//! it. It is kept, because a host that has not yet probed an endpoint needs a way\\n//! to say so — and a dashboard rendering \\\"unknown\\\" is honest where rendering\\n//! \\\"healthy\\\" by default is not.\\n\\n/// What a probe made of an endpoint.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum EndpointStatus {\\n /// Responded, and quickly enough.\\n Healthy,\\n /// Responded, but slowly.\\n Degraded,\\n /// Responded with a failure, or the connection failed.\\n Down,\\n /// Did not respond inside the allotted time.\\n Timeout,\\n /// Not yet probed. Never inferred — only stated.\\n Unknown,\\n}\\n\\nimpl EndpointStatus {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Healthy => \\\"healthy\\\",\\n Self::Degraded => \\\"degraded\\\",\\n Self::Down => \\\"down\\\",\\n Self::Timeout => \\\"timeout\\\",\\n Self::Unknown => \\\"unknown\\\",\\n }\\n }\\n\\n /// Whether this status should worry somebody.\\n #[must_use]\\n pub const fn is_unhealthy(self) -> bool {\\n matches!(self, Self::Degraded | Self::Down | Self::Timeout)\\n }\\n}\\n\\n/// What the host's request actually did.\\n///\\n/// An enum rather than an error string, so a timeout is a *fact the caller\\n/// reports* instead of something this module infers from prose it did not write.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub enum Outcome {\\n /// The request completed with an HTTP status.\\n Responded {\\n /// The HTTP status code.\\n status_code: u16,\\n },\\n /// The request was aborted for exceeding its deadline.\\n TimedOut,\\n /// The request failed before producing a status.\\n Failed {\\n /// Whatever the host can say about why.\\n error: String,\\n },\\n}\\n\\n/// An endpoint to probe.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct Endpoint {\\n /// Full URL.\\n pub url: String,\\n /// Which component it reports for.\\n pub component_name: String,\\n /// Which helix node that component belongs to.\\n pub node: u32,\\n}\\n\\n/// One probe's finding.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct EndpointCheck {\\n /// Which endpoint.\\n pub url: String,\\n /// Which component.\\n pub component_name: String,\\n /// Which node.\\n pub node: u32,\\n /// The verdict.\\n pub status: EndpointStatus,\\n /// HTTP status, when there was one.\\n pub status_code: Option<u16>,\\n /// How long it took.\\n pub latency_ms: f64,\\n /// When, in milliseconds since the Unix epoch.\\n pub checked_at_ms: f64,\\n /// Why it failed, when it did.\\n pub error: Option<String>,\\n}\\n\\n/// A slow-but-alive threshold that cannot contradict the timeout.\\n///\\n/// Returns 40% of the timeout, floored at 250 ms. The `.ts`'s fixed 2000 ms was\\n/// meaningless whenever the timeout was lower, because the request aborted before\\n/// it could ever be classified slow — the `Degraded` state simply became\\n/// unreachable, silently.\\n#[must_use]\\npub fn degraded_threshold_for(timeout_ms: f64) -> f64 {\\n (timeout_ms * 0.4).max(250.0)\\n}\\n\\n/// Classify one outcome.\\n///\\n/// A 2xx/3xx response is healthy unless it was slow; anything else is down. The\\n/// latency test applies only to a successful response, because a slow 500 is\\n/// down, not degraded — reporting it as degraded would understate an outage.\\n#[must_use]\\npub fn classify(outcome: &Outcome, latency_ms: f64, degraded_above_ms: f64) -> EndpointStatus {\\n match outcome {\\n Outcome::TimedOut => EndpointStatus::Timeout,\\n Outcome::Failed { .. } => EndpointStatus::Down,\\n Outcome::Responded { status_code } => {\\n if (200..400).contains(status_code) {\\n if latency_ms > degraded_above_ms {\\n EndpointStatus::Degraded\\n } else {\\n EndpointStatus::Healthy\\n }\\n } else {\\n EndpointStatus::Down\\n }\\n }\\n }\\n}\\n\\n/// Build a check from an endpoint and what the host's request did.\\n#[must_use]\\npub fn check(\\n endpoint: &Endpoint,\\n outcome: &Outcome,\\n latency_ms: f64,\\n checked_at_ms: f64,\\n degraded_above_ms: f64,\\n) -> EndpointCheck {\\n EndpointCheck {\\n url: endpoint.url.clone(),\\n component_name: endpoint.component_name.clone(),\\n node: endpoint.node,\\n status: classify(outcome, latency_ms, degraded_above_ms),\\n status_code: match outcome {\\n Outcome::Responded { status_code } => Some(*status_code),\\n _ => None,\\n },\\n latency_ms,\\n checked_at_ms,\\n error: match outcome {\\n Outcome::Failed { error } => Some(error.clone()),\\n Outcome::TimedOut => Some(format!(\\\"timed out after {latency_ms:.0}ms\\\")),\\n Outcome::Responded { .. } => None,\\n },\\n }\\n}\\n\\n/// The default endpoints, mirroring the `.ts`.\\n///\\n/// A starting set, not a registry. The `.ts` carries a comment saying production\\n/// discovers these from a `component_backlinks` view — which remains true, and is\\n/// the host's job because it is a database read.\\n#[must_use]\\npub fn default_endpoints(base_url: &str) -> Vec<Endpoint> {\\n let base = base_url.trim_end_matches('/');\\n [\\n (\\\"mzizi-tokens\\\", 1),\\n (\\\"mzizi-section\\\", 5),\\n (\\\"mzizi-wallet-gate\\\", 4),\\n (\\\"wallet-page\\\", 6),\\n ]\\n .into_iter()\\n .map(|(name, node)| Endpoint {\\n url: format!(\\\"{base}/api/health/{name}\\\"),\\n component_name: name.to_owned(),\\n node,\\n })\\n .collect()\\n}\\n\\n/// The worst status in a set of checks.\\n///\\n/// \\\"Worst\\\" rather than \\\"most common\\\", because one endpoint being down is an\\n/// outage even when nine are healthy — an average would hide exactly the case\\n/// worth seeing.\\n#[must_use]\\npub fn worst_status(checks: &[EndpointCheck]) -> EndpointStatus {\\n checks\\n .iter()\\n .map(|c| c.status)\\n .max_by_key(|s| match s {\\n EndpointStatus::Healthy => 0,\\n EndpointStatus::Unknown => 1,\\n EndpointStatus::Degraded => 2,\\n EndpointStatus::Timeout => 3,\\n EndpointStatus::Down => 4,\\n })\\n .unwrap_or(EndpointStatus::Unknown)\\n}\\n\"}],\"contract\":null}",
|
|
1201
|
+
"mzizi-avatar-stack": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-avatar-stack\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Overlapping +N social-proof avatar stack — the going/attendee cue. Ring-separated avatars overlap with negative margin, each an image or an initials fallback, showing up to `max` before a +N overflow bubble, with an optional trailing <total> <label> in 13px muted. Purely presentational and server-safe (no harness, no client directive) so it renders inside React Server Components. The group carries an accessible summary label (e.g. \\\"128 going\\\") while the decorative avatars are aria-hidden. sm / md sizes.\",\"crate\":{\"name\":\"mzizi-brand\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n3-brand/mzizi-avatar-stack.rs\",\"type\":\"registry:rust\",\"content\":\"//! MZIZI AVATAR STACK — N3 brand, Dioxus. A Mzizi Roots component.\\n//!\\n//! The Rust sibling of `mzizi-avatar-stack.tsx`: an overlapping social-proof stack that shows\\n//! up to `max` avatars, then a `+N` overflow bubble, then an optional \\\"<total> <label>\\\" line.\\n//! Same `data-slot`, same classes, same accessible summary.\\n//!\\n//! Purely presentational and server-safe, like the `.tsx`. The group carries the summary as\\n//! its accessible name; the avatars themselves are decorative (`aria-hidden`).\\n//!\\n//! # `initials` splits on Unicode whitespace and never inside a code point\\n//!\\n//! The `.tsx` takes `w[0]`, which is a UTF-16 code unit: a name starting with an emoji or a\\n//! character outside the Basic Multilingual Plane yields half a surrogate pair. [`initials`]\\n//! takes the first `char` of each word instead. For every name the `.tsx` handles correctly,\\n//! the two agree.\\n\\nuse dioxus::prelude::*;\\n\\n/// The component's contract, in the clause grammar of the Mzizi language's `contract … end`\\n/// block (mzizi-dev/mzizi `design/RFC-0006-contracts.md`). `mzizi-brand`'s contract suite\\n/// evaluates every clause against this component's server-rendered markup and fails on any\\n/// clause it cannot evaluate.\\npub const CONTRACT: &str = r#\\\"contract\\n slot is \\\"mzizi-avatar-stack\\\"\\n role is \\\"group\\\"\\n label not_empty\\n max is 4\\n every avatar_stack_size class contains \\\"rounded-full\\\"\\nend\\\"#;\\n\\n/// One person in the stack.\\n#[derive(Clone, Debug, PartialEq)]\\npub struct AvatarPerson {\\n /// Display name; initials are derived from it when there is no image.\\n pub name: String,\\n /// Image URL.\\n pub src: Option<String>,\\n}\\n\\n/// Avatar diameter.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]\\npub enum AvatarStackSize {\\n /// 24px avatars.\\n Sm,\\n /// 32px avatars.\\n #[default]\\n Md,\\n}\\n\\nimpl AvatarStackSize {\\n /// Every size, for exhaustive checks.\\n pub const ALL: [Self; 2] = [Self::Sm, Self::Md];\\n\\n /// The `size === \\\"sm\\\"` branch of the `.tsx`.\\n #[must_use]\\n pub const fn size_classes(self) -> &'static str {\\n match self {\\n Self::Sm => \\\"size-6 text-[10px]\\\",\\n Self::Md => \\\"size-8 text-[11px]\\\",\\n }\\n }\\n\\n /// The classes of an initials or overflow bubble at this size.\\n #[must_use]\\n pub fn bubble_classes(self) -> String {\\n format!(\\n \\\"inline-flex items-center justify-center rounded-full bg-muted font-semibold text-muted-foreground ring-2 ring-card {}\\\",\\n self.size_classes()\\n )\\n }\\n\\n /// The classes of an image avatar at this size.\\n #[must_use]\\n pub fn image_classes(self) -> String {\\n format!(\\n \\\"rounded-full object-cover ring-2 ring-card {}\\\",\\n self.size_classes()\\n )\\n }\\n\\n /// The value the `.tsx` accepts for `size`.\\n #[must_use]\\n pub const fn slug(self) -> &'static str {\\n match self {\\n Self::Sm => \\\"sm\\\",\\n Self::Md => \\\"md\\\",\\n }\\n }\\n}\\n\\n/// Up to two uppercase initials, one per leading word.\\n#[must_use]\\npub fn initials(name: &str) -> String {\\n name.split_whitespace()\\n .filter_map(|word| word.chars().next())\\n .take(2)\\n .flat_map(char::to_uppercase)\\n .collect()\\n}\\n\\n/// What the stack shows: how many avatars, how large the `+N` overflow is, and the summary.\\n#[derive(Clone, Debug, PartialEq, Eq)]\\npub struct StackLayout {\\n /// How many avatars are drawn.\\n pub shown: usize,\\n /// The `+N` overflow count; zero hides the bubble.\\n pub overflow: usize,\\n /// The accessible summary, e.g. \\\"128 going\\\".\\n pub summary: String,\\n}\\n\\n/// The `.tsx`'s arithmetic: `shown = people.slice(0, max)`, `count = total ?? people.length`,\\n/// `overflow = count - shown.length`, and the summary with or without a label.\\n///\\n/// Saturating where the `.tsx` would go negative (a `total` smaller than the people drawn),\\n/// which the `.tsx` hides only because it tests `overflow > 0`.\\n#[must_use]\\npub fn stack_layout(\\n people: usize,\\n max: usize,\\n total: Option<usize>,\\n label: Option<&str>,\\n) -> StackLayout {\\n let shown = people.min(max);\\n let count = total.unwrap_or(people);\\n let summary = match label {\\n Some(l) if !l.is_empty() => format!(\\\"{count} {l}\\\"),\\n _ => count.to_string(),\\n };\\n StackLayout {\\n shown,\\n overflow: count.saturating_sub(shown),\\n summary,\\n }\\n}\\n\\n/// Join a base class string with a consumer's extra classes, without a trailing space.\\nfn join(base: &str, extra: &str) -> String {\\n if extra.is_empty() {\\n base.to_owned()\\n } else {\\n format!(\\\"{base} {extra}\\\")\\n }\\n}\\n\\n/// Props for [`MziziAvatarStack`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziAvatarStackProps {\\n /// The people to draw, in order.\\n pub people: Vec<AvatarPerson>,\\n /// Max avatars before collapsing into a `+N` bubble.\\n #[props(default = 4)]\\n pub max: usize,\\n /// Total count for the bubble and summary; defaults to `people.len()`.\\n #[props(default)]\\n pub total: Option<usize>,\\n /// Trailing label after the count. `None` or empty hides the visible line.\\n #[props(default = Some(\\\"going\\\".to_string()))]\\n pub label: Option<String>,\\n /// Avatar diameter.\\n #[props(default)]\\n pub size: AvatarStackSize,\\n /// Extra classes, appended last.\\n #[props(default)]\\n pub class: String,\\n}\\n\\n/// Overlapping avatars with a `+N` overflow bubble and a count label.\\n#[component]\\npub fn MziziAvatarStack(props: MziziAvatarStackProps) -> Element {\\n let layout = stack_layout(\\n props.people.len(),\\n props.max,\\n props.total,\\n props.label.as_deref(),\\n );\\n let bubble = props.size.bubble_classes();\\n let image = props.size.image_classes();\\n let show_label = props.label.as_deref().is_some_and(|l| !l.is_empty());\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-avatar-stack\\\",\\n role: \\\"group\\\",\\n \\\"aria-label\\\": \\\"{layout.summary}\\\",\\n class: join(\\\"flex items-center gap-2\\\", &props.class),\\n div { class: \\\"flex -space-x-2\\\", \\\"aria-hidden\\\": \\\"true\\\",\\n for (i, person) in props.people.iter().take(layout.shown).enumerate() {\\n if let Some(src) = &person.src {\\n img { key: \\\"{i}\\\", src: \\\"{src}\\\", alt: \\\"\\\", class: \\\"{image}\\\" }\\n } else {\\n span { key: \\\"{i}\\\", class: \\\"{bubble}\\\", {initials(&person.name)} }\\n }\\n }\\n if layout.overflow > 0 {\\n span { class: \\\"{bubble}\\\", \\\"+{layout.overflow}\\\" }\\n }\\n }\\n if show_label {\\n span { class: \\\"text-[13px] text-muted-foreground\\\", \\\"aria-hidden\\\": \\\"true\\\", \\\"{layout.summary}\\\" }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn initials_take_the_first_two_words() {\\n assert_eq!(initials(\\\"Tendai Moyo\\\"), \\\"TM\\\");\\n assert_eq!(initials(\\\" chipo ruvimbo mapfumo \\\"), \\\"CR\\\");\\n assert_eq!(initials(\\\"Nyasha\\\"), \\\"N\\\");\\n assert_eq!(initials(\\\"\\\"), \\\"\\\");\\n }\\n\\n #[test]\\n fn initials_never_split_a_code_point() {\\n assert_eq!(initials(\\\"Émile Zola\\\"), \\\"ÉZ\\\");\\n }\\n\\n #[test]\\n fn overflow_counts_everyone_not_drawn() {\\n let l = stack_layout(6, 4, None, Some(\\\"going\\\"));\\n assert_eq!(l.shown, 4);\\n assert_eq!(l.overflow, 2);\\n assert_eq!(l.summary, \\\"6 going\\\");\\n }\\n\\n #[test]\\n fn an_explicit_total_drives_the_bubble_and_summary() {\\n let l = stack_layout(3, 4, Some(128), Some(\\\"going\\\"));\\n assert_eq!(l.shown, 3);\\n assert_eq!(l.overflow, 125);\\n assert_eq!(l.summary, \\\"128 going\\\");\\n }\\n\\n #[test]\\n fn no_label_leaves_the_bare_count() {\\n assert_eq!(stack_layout(2, 4, None, None).summary, \\\"2\\\");\\n assert_eq!(stack_layout(2, 4, None, Some(\\\"\\\")).summary, \\\"2\\\");\\n }\\n\\n #[test]\\n fn a_total_below_the_drawn_count_never_underflows() {\\n assert_eq!(stack_layout(4, 4, Some(1), None).overflow, 0);\\n }\\n}\\n\"}],\"contract\":\"contract\\n slot is \\\"mzizi-avatar-stack\\\"\\n role is \\\"group\\\"\\n label not_empty\\n max is 4\\n every avatar_stack_size class contains \\\"rounded-full\\\"\\nend\"}",
|
|
1202
|
+
"mzizi-bottom-nav": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-bottom-nav\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Mobile-only bottom navigation bar for Nyuchi ecosystem apps. Supports a center FAB (floating action button) — a raised button in the app mineral accent colour with a glow shadow. The center FAB is the primary creation entry point, making the \\\"create\\\" action instantly recognizable across all apps.\",\"crate\":{\"name\":\"mzizi-shell\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n7-shell/mzizi-bottom-nav.rs\",\"type\":\"registry:rust\",\"content\":\"//! NYUCHI BOTTOM NAV — N7 shell, Dioxus.\\n//!\\n//! The Rust sibling of `mzizi-bottom-nav.tsx`: 5-item bottom navigation with an optional\\n//! center FAB, sharing its contract — same `data-slot`, same active-state detection.\\n\\nuse dioxus::prelude::*;\\n\\n/// Bottom-nav item. `is_fab` marks the raised center action button.\\n#[derive(Clone, Debug, PartialEq)]\\npub struct BottomNavItem {\\n /// Unique identifier.\\n pub id: String,\\n /// Display label.\\n pub label: String,\\n /// Route path.\\n pub href: String,\\n /// Whether this item is the center FAB rather than a link.\\n pub is_fab: bool,\\n}\\n\\n/// Mirrors the .tsx `isActive`: an explicit `active_id` wins; otherwise `/` matches only\\n/// exactly, and any other href matches itself or any of its sub-paths.\\npub fn is_active(item: &BottomNavItem, active_id: Option<&str>, pathname: &str) -> bool {\\n if let Some(id) = active_id {\\n return id == item.id;\\n }\\n if item.href == \\\"/\\\" {\\n return pathname == \\\"/\\\";\\n }\\n pathname == item.href || pathname.starts_with(&format!(\\\"{}/\\\", item.href))\\n}\\n\\nconst FAB: &str = \\\"flex size-[52px] -translate-y-6 items-center justify-center rounded-full \\\\\\n bg-[var(--brand-accent,var(--color-primary,#00B0FF))] \\\\\\n shadow-[0_4px_20px_var(--brand-accent-glow,rgba(100,255,218,0.3))] \\\\\\n transition-transform active:scale-95\\\";\\n\\nconst NAV_ITEM: &str = \\\"relative flex min-w-[56px] flex-col items-center gap-[3px] py-1 \\\\\\n text-[10px] font-medium transition-colors\\\";\\n\\n/// Props for [`MziziBottomNav`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziBottomNavProps {\\n /// The nav items to render, including at most one FAB.\\n pub items: Vec<BottomNavItem>,\\n /// Override active detection (useful for client-side routing).\\n pub active_id: Option<String>,\\n /// The current route, used for active detection when `active_id` is absent.\\n pub pathname: String,\\n /// Fired when the center FAB is pressed.\\n #[props(default)]\\n pub on_fab_click: EventHandler<()>,\\n}\\n\\n/// Fixed bottom navigation bar with an optional raised center FAB.\\n#[component]\\npub fn MziziBottomNav(props: MziziBottomNavProps) -> Element {\\n rsx! {\\n nav {\\n \\\"data-slot\\\": \\\"mzizi-bottom-nav\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-bottom-nav\\\",\\n class: \\\"fixed inset-x-0 bottom-0 z-50 flex h-20 items-center justify-around \\\\\\n border-t border-border bg-card/90 backdrop-blur-xl \\\\\\n pb-[env(safe-area-inset-bottom)] md:hidden\\\",\\n for item in props.items.iter() {\\n if item.is_fab {\\n button {\\n key: \\\"{item.id}\\\",\\n \\\"aria-label\\\": \\\"{item.label}\\\",\\n class: \\\"{FAB}\\\",\\n onclick: move |_| props.on_fab_click.call(()),\\n \\\"+\\\"\\n }\\n } else {\\n a {\\n key: \\\"{item.id}\\\",\\n href: \\\"{item.href}\\\",\\n class: \\\"{NAV_ITEM}\\\",\\n if is_active(item, props.active_id.as_deref(), &props.pathname) {\\n span { class: \\\"absolute -top-[9px] h-[2.5px] w-7 rounded-full bg-[var(--brand-accent,var(--color-primary,#00B0FF))]\\\" }\\n }\\n span { \\\"{item.label}\\\" }\\n }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n fn item(id: &str, href: &str) -> BottomNavItem {\\n BottomNavItem {\\n id: id.into(),\\n label: id.into(),\\n href: href.into(),\\n is_fab: false,\\n }\\n }\\n\\n #[test]\\n fn root_href_matches_only_exactly() {\\n let home = item(\\\"home\\\", \\\"/\\\");\\n assert!(is_active(&home, None, \\\"/\\\"));\\n assert!(!is_active(&home, None, \\\"/feed\\\"));\\n }\\n\\n #[test]\\n fn non_root_href_matches_itself_and_sub_paths() {\\n let feed = item(\\\"feed\\\", \\\"/feed\\\");\\n assert!(is_active(&feed, None, \\\"/feed\\\"));\\n assert!(is_active(&feed, None, \\\"/feed/123\\\"));\\n assert!(!is_active(&feed, None, \\\"/feeds\\\"));\\n }\\n\\n #[test]\\n fn explicit_active_id_overrides_pathname() {\\n let feed = item(\\\"feed\\\", \\\"/feed\\\");\\n assert!(is_active(&feed, Some(\\\"feed\\\"), \\\"/somewhere-else\\\"));\\n assert!(!is_active(&feed, Some(\\\"other\\\"), \\\"/feed\\\"));\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1203
|
+
"mzizi-changelog-renderer": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-changelog-renderer\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Changelog renderer for N10. Reads from the public.changelog table and renders a visual timeline of releases with affected ecosystem nodes (colour-coded by axis), components added/modified/deprecated, and version labels.\",\"crate\":{\"name\":\"mzizi-docs\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n10-documentation/mzizi-changelog-renderer.rs\",\"type\":\"registry:rust\",\"content\":\"//! NYUCHI CHANGELOG RENDERER — N10 documentation, Dioxus.\\n//!\\n//! The Rust sibling of `mzizi-changelog-renderer.tsx`, sharing its contract: the same\\n//! `data-slot`, the same timeline structure, and the same class strings, so a Dioxus app\\n//! renders markup a React-authored stylesheet already styles.\\n//!\\n//! # The node table went stale, again\\n//!\\n//! The `.tsx` carries two hardcoded maps, `NODE_LABELS` and `NODE_AXIS`, both stopping at\\n//! N10. `mzizi-ai-context` next door has the identical defect and this file makes three in\\n//! one node — which is the argument for treating a node table as DATA rather than as a\\n//! literal, everywhere it appears.\\n//!\\n//! The consequence here is visible rather than merely wrong. `NODE_AXIS[11]` is undefined, so\\n//! `AXIS_COLOURS[NODE_AXIS[n] ?? \\\"horizontal\\\"]` paints N11 and N12 cobalt — the horizontal\\n//! colour — while the `title` beside it reads \\\"N11 — Unknown (unknown axis)\\\". A changelog\\n//! entry touching the discovery rung renders with a confident wrong colour and a tooltip\\n//! admitting it does not know.\\n//!\\n//! So [`NodeStyle`] rows are a parameter. [`default_node_styles`] covers N1–N12 and is a\\n//! DEFAULT, not a definition — a caller reading `/api/v1/architecture` gets one that cannot\\n//! go stale.\\n//!\\n//! # The axis words are gone, the colours are not\\n//!\\n//! `horizontal` / `vertical` / `depth` / `outlier` are the retired layer-and-axis scheme.\\n//! The four colour classes they selected are real and shipped, so [`NodeAccent`] keeps the\\n//! colours and names them by their mineral — which is what they actually are — instead of by\\n//! a model this repo no longer uses. Every class string is byte-identical to the `.tsx`; the\\n//! contract test asserts each one appears there.\\n\\nuse dioxus::prelude::*;\\n\\n/// Which mineral a node's badge is painted in.\\n///\\n/// Named for the mineral rather than the retired axis it used to stand for. The class\\n/// strings are unchanged, so nothing renders differently.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\\npub enum NodeAccent {\\n /// Cobalt. Was the `horizontal` axis, and the fallback for an unknown node.\\n #[default]\\n Cobalt,\\n /// Tanzanite. Was the `vertical` axis.\\n Tanzanite,\\n /// Malachite. Was the `depth` axis.\\n Malachite,\\n /// Gold. Was the `outlier` axis.\\n Gold,\\n}\\n\\nimpl NodeAccent {\\n /// The badge classes. Identical to the `.tsx` `AXIS_COLOURS` entries.\\n #[must_use]\\n pub const fn classes(self) -> &'static str {\\n match self {\\n Self::Cobalt => \\\"bg-[var(--color-cobalt)]/10 text-[var(--color-cobalt)]\\\",\\n Self::Tanzanite => \\\"bg-[var(--color-tanzanite)]/10 text-[var(--color-tanzanite)]\\\",\\n Self::Malachite => \\\"bg-[var(--color-malachite)]/10 text-[var(--color-malachite)]\\\",\\n Self::Gold => \\\"bg-[var(--color-gold)]/10 text-[var(--color-gold)]\\\",\\n }\\n }\\n}\\n\\n/// How one node renders in a badge.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct NodeStyle {\\n /// Node number. Uncapped.\\n pub number: u16,\\n /// Short label, e.g. `Primitives`.\\n pub label: String,\\n /// Badge colour.\\n pub accent: NodeAccent,\\n}\\n\\nimpl NodeStyle {\\n /// Build a row.\\n #[must_use]\\n pub fn new(number: u16, label: &str, accent: NodeAccent) -> Self {\\n Self {\\n number,\\n label: label.to_owned(),\\n accent,\\n }\\n }\\n}\\n\\n/// The node set as it stands. A DEFAULT — see the module docs.\\n///\\n/// N1–N10 keep exactly the colours the `.tsx` gave them. N11 and N12 are added because the\\n/// `.tsx` rendered them as \\\"Unknown\\\" in a horizontal-axis colour it had not been asked for.\\n#[must_use]\\npub fn default_node_styles() -> Vec<NodeStyle> {\\n vec![\\n NodeStyle::new(1, \\\"Tokens\\\", NodeAccent::Tanzanite),\\n NodeStyle::new(2, \\\"Primitives\\\", NodeAccent::Cobalt),\\n NodeStyle::new(3, \\\"Brand\\\", NodeAccent::Cobalt),\\n NodeStyle::new(4, \\\"Safety\\\", NodeAccent::Tanzanite),\\n NodeStyle::new(5, \\\"Resilience\\\", NodeAccent::Tanzanite),\\n NodeStyle::new(6, \\\"Pages\\\", NodeAccent::Cobalt),\\n NodeStyle::new(7, \\\"Shell\\\", NodeAccent::Cobalt),\\n NodeStyle::new(8, \\\"Assurance\\\", NodeAccent::Malachite),\\n NodeStyle::new(9, \\\"Fundi\\\", NodeAccent::Gold),\\n NodeStyle::new(10, \\\"Documentation\\\", NodeAccent::Gold),\\n NodeStyle::new(11, \\\"Discovery\\\", NodeAccent::Gold),\\n NodeStyle::new(12, \\\"Skills\\\", NodeAccent::Gold),\\n ]\\n}\\n\\n/// One released version.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct ChangelogEntry {\\n /// Semantic version.\\n pub version: String,\\n /// One-line headline.\\n pub title: String,\\n /// Longer prose.\\n pub description: String,\\n /// Release date, already formatted by the caller.\\n ///\\n /// A pre-formatted string rather than a date type, matching the `.tsx`, which renders\\n /// `entry.date` verbatim. Formatting is the host's: it knows the viewer's locale and\\n /// this component does not.\\n pub date: String,\\n /// Node numbers this release touched. Mirrors `changelog.nodes_affected`.\\n pub nodes_affected: Vec<u16>,\\n /// Components added.\\n pub components_added: Vec<String>,\\n /// Components changed.\\n pub components_modified: Vec<String>,\\n /// Components deprecated.\\n pub components_deprecated: Vec<String>,\\n}\\n\\n/// Look a node up, falling back the way the `.tsx` does.\\nfn style_for(styles: &[NodeStyle], n: u16) -> (String, NodeAccent) {\\n styles.iter().find(|s| s.number == n).map_or_else(\\n // The `.tsx` says \\\"Unknown\\\" here and paints the horizontal colour anyway. Saying\\n // the node is unrecognised and colouring it as if it were is the part worth not\\n // reproducing: the label admits the gap, and the default accent is stated.\\n || (format!(\\\"N{n} — unrecognised node\\\"), NodeAccent::default()),\\n |s| (format!(\\\"N{} — {}\\\", s.number, s.label), s.accent),\\n )\\n}\\n\\nconst ROOT: &str = \\\"flex flex-col gap-8\\\";\\nconst ARTICLE: &str = \\\"relative border-l-2 border-border pl-6\\\";\\nconst DOT: &str = \\\"absolute top-1 -left-[5px] size-2 rounded-full bg-primary\\\";\\nconst VERSION: &str = \\\"font-mono text-sm font-bold text-primary\\\";\\nconst DATE: &str = \\\"text-xs text-muted-foreground\\\";\\nconst TITLE: &str = \\\"mt-1 text-lg font-semibold\\\";\\nconst DESCRIPTION: &str = \\\"mt-1 text-sm text-muted-foreground\\\";\\nconst NODE_BADGE: &str = \\\"rounded-full px-2 py-0.5 text-xs font-medium\\\";\\nconst ADDED: &str = \\\"rounded bg-[var(--status-success,#64FFDA)]/10 px-1.5 py-0.5 text-xs text-[var(--status-success,#22C55E)]\\\";\\nconst MODIFIED: &str =\\n \\\"rounded bg-[var(--color-cobalt)]/10 px-1.5 py-0.5 text-xs text-[var(--color-cobalt)]\\\";\\nconst DEPRECATED: &str = \\\"rounded bg-[var(--status-warning,#F59E0B)]/10 px-1.5 py-0.5 text-xs text-[var(--status-warning,#F59E0B)] line-through\\\";\\n\\n/// Join a base class string with a consumer's extra classes.\\nfn join(base: &str, extra: &str) -> String {\\n if extra.is_empty() {\\n base.to_owned()\\n } else {\\n format!(\\\"{base} {extra}\\\")\\n }\\n}\\n\\n/// Props for [`ChangelogRenderer`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct ChangelogRendererProps {\\n /// Entries, newest first.\\n pub entries: Vec<ChangelogEntry>,\\n /// Node styling. Empty means [`default_node_styles`].\\n #[props(default)]\\n pub node_styles: Vec<NodeStyle>,\\n /// Extra classes, merged onto the root.\\n #[props(default)]\\n pub class: String,\\n /// Any other root attributes.\\n #[props(extends = GlobalAttributes)]\\n pub attributes: Vec<Attribute>,\\n}\\n\\n/// The database changelog as a visual timeline.\\n#[component]\\npub fn ChangelogRenderer(props: ChangelogRendererProps) -> Element {\\n let owned = default_node_styles();\\n let styles: &[NodeStyle] = if props.node_styles.is_empty() {\\n &owned\\n } else {\\n &props.node_styles\\n };\\n let total = props.entries.len();\\n\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-changelog-renderer\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-changelog-renderer\\\",\\n class: join(ROOT, &props.class),\\n role: \\\"feed\\\",\\n \\\"aria-label\\\": \\\"Changelog\\\",\\n ..props.attributes,\\n\\n for (index , entry) in props.entries.iter().enumerate() {\\n article {\\n key: \\\"{entry.version}\\\",\\n class: ARTICLE,\\n // `role=\\\"feed\\\"` requires its articles to carry their position, or a\\n // screen reader announces an unbounded stream with no sense of where the\\n // user is. The `.tsx` sets `role=\\\"feed\\\"` and omits both.\\n \\\"aria-posinset\\\": \\\"{index + 1}\\\",\\n \\\"aria-setsize\\\": \\\"{total}\\\",\\n \\\"aria-labelledby\\\": \\\"changelog-{entry.version}\\\",\\n\\n div { class: DOT }\\n\\n div { class: \\\"flex flex-wrap items-baseline gap-3\\\",\\n span { class: VERSION, \\\"{entry.version}\\\" }\\n time { class: DATE, \\\"{entry.date}\\\" }\\n }\\n\\n h3 { id: \\\"changelog-{entry.version}\\\", class: TITLE, \\\"{entry.title}\\\" }\\n p { class: DESCRIPTION, \\\"{entry.description}\\\" }\\n\\n if !entry.nodes_affected.is_empty() {\\n div {\\n class: \\\"mt-2 flex flex-wrap gap-1\\\",\\n \\\"aria-label\\\": \\\"Nodes affected\\\",\\n for n in entry.nodes_affected.iter().copied() {\\n {\\n let (label, accent) = style_for(styles, n);\\n rsx! {\\n span {\\n key: \\\"{n}\\\",\\n class: \\\"{NODE_BADGE} {accent.classes()}\\\",\\n title: \\\"{label}\\\",\\n \\\"N{n}\\\"\\n }\\n }\\n }\\n }\\n }\\n }\\n\\n if !entry.components_added.is_empty() {\\n div {\\n class: \\\"mt-2 flex flex-wrap gap-1\\\",\\n \\\"aria-label\\\": \\\"Components added\\\",\\n for c in entry.components_added.iter() {\\n span { key: \\\"{c}\\\", class: ADDED, \\\"+{c}\\\" }\\n }\\n }\\n }\\n\\n if !entry.components_modified.is_empty() {\\n div {\\n class: \\\"mt-1 flex flex-wrap gap-1\\\",\\n \\\"aria-label\\\": \\\"Components modified\\\",\\n for c in entry.components_modified.iter() {\\n span { key: \\\"{c}\\\", class: MODIFIED, \\\"~{c}\\\" }\\n }\\n }\\n }\\n\\n if !entry.components_deprecated.is_empty() {\\n div {\\n class: \\\"mt-1 flex flex-wrap gap-1\\\",\\n \\\"aria-label\\\": \\\"Components deprecated\\\",\\n for c in entry.components_deprecated.iter() {\\n span { key: \\\"{c}\\\", class: DEPRECATED, \\\"{c}\\\" }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn every_default_node_has_a_label_and_no_duplicates() {\\n let styles = default_node_styles();\\n assert_eq!(styles.len(), 12);\\n for s in &styles {\\n assert!(!s.label.is_empty());\\n }\\n let mut numbers: Vec<u16> = styles.iter().map(|s| s.number).collect();\\n numbers.sort_unstable();\\n numbers.dedup();\\n assert_eq!(numbers.len(), styles.len());\\n }\\n\\n #[test]\\n fn the_defaults_reach_past_n10() {\\n let styles = default_node_styles();\\n assert!(styles.iter().any(|s| s.number == 11));\\n assert!(styles.iter().any(|s| s.number == 12));\\n }\\n\\n #[test]\\n fn n1_to_n10_keep_the_colours_the_tsx_gave_them() {\\n let styles = default_node_styles();\\n let accent = |n: u16| styles.iter().find(|s| s.number == n).unwrap().accent;\\n // vertical → tanzanite\\n for n in [1, 4, 5] {\\n assert_eq!(accent(n), NodeAccent::Tanzanite);\\n }\\n // horizontal → cobalt\\n for n in [2, 3, 6, 7] {\\n assert_eq!(accent(n), NodeAccent::Cobalt);\\n }\\n assert_eq!(accent(8), NodeAccent::Malachite);\\n for n in [9, 10] {\\n assert_eq!(accent(n), NodeAccent::Gold);\\n }\\n }\\n\\n #[test]\\n fn an_unknown_node_says_so_rather_than_claiming_a_colour_it_was_not_given() {\\n let (label, accent) = style_for(&default_node_styles(), 99);\\n assert!(label.contains(\\\"unrecognised\\\"));\\n assert_eq!(accent, NodeAccent::default());\\n }\\n\\n #[test]\\n fn a_caller_supplied_table_replaces_the_default() {\\n let custom = vec![NodeStyle::new(42, \\\"Future\\\", NodeAccent::Malachite)];\\n let (label, accent) = style_for(&custom, 42);\\n assert_eq!(label, \\\"N42 — Future\\\");\\n assert_eq!(accent, NodeAccent::Malachite);\\n }\\n\\n #[test]\\n fn accent_classes_are_distinct() {\\n let all = [\\n NodeAccent::Cobalt,\\n NodeAccent::Tanzanite,\\n NodeAccent::Malachite,\\n NodeAccent::Gold,\\n ];\\n for (i, a) in all.iter().enumerate() {\\n for b in &all[i + 1..] {\\n assert_ne!(a.classes(), b.classes());\\n }\\n }\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1204
|
+
"mzizi-chaos": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-chaos\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"Reactive chaos diagnostics — N8 Assurance (Z-axis/depth). Runs through the entire 3D architecture, able to inject failures at any component on any horizontal layer, through any vertical spine. Two modes: (1) Injection — random error/latency at configurable probability per layer intersection. (2) Reactive — when a real error is caught anywhere in the 3D space, probe adjacent systems for blast radius. The depth dimension that proves X and Y axes work.\",\"crate\":{\"name\":\"mzizi-assurance\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n8-assurance/mzizi-chaos.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N8 assurance — chaos injection and blast-radius diagnosis.\\n//!\\n//! The Rust implementation of `mzizi-chaos`. The `.tsx` extension is misleading:\\n//! all but the last thirty lines of that file are pure functions, and the React\\n//! context around them is a thin wrapper. This is the logic half.\\n//!\\n//! # What this owns\\n//!\\n//! Whether to inject, into what, and what a set of probe results means. Not the\\n//! probing: the host performs the requests, because a core with no I/O is the\\n//! whole point of N8 being a shared core.\\n//!\\n//! # Five defects in the TypeScript, fixed here\\n//!\\n//! **1. `targetNodes` was declared and never read.** The field is documented\\n//! \\\"Nodes to target (empty = all)\\\", and nothing in the file consults it — so a\\n//! consumer scoping chaos to N5 got chaos in every node, having taken the\\n//! deliberate step of narrowing it. A configuration option that silently does\\n//! nothing is worse than an absent one, because it buys confidence. [`ChaosConfig::targets`]\\n//! is now the gate and every injection decision takes the node.\\n//!\\n//! **2. `featureFlag` was declared and never read either.** Documented as a key\\n//! \\\"for remote control\\\", with no remote control anywhere in the component. Kept as\\n//! [`ChaosConfig::feature_flag`] because a host genuinely can resolve a flag and\\n//! pass the result in, and named in the docs as what it is: an input to the host's\\n//! decision, not something this core acts on.\\n//!\\n//! **3. The blast-radius bands could not all be reached, and the unreachable one\\n//! was the calm one.** `errorCount < probes.length / 2` gives, for the default two\\n//! probes: 0 errors → `isolated`, 1 → `systemic`, 2 → `systemic`. `partial` is\\n//! unreachable, so ONE failing endpoint out of two recommended \\\"Activate incident\\n//! response\\\". A classifier whose middle band is dead and whose error is toward\\n//! paging is one people learn to disregard. [`classify_blast_radius`] uses the\\n//! fraction, with the boundaries stated.\\n//!\\n//! **4. The default endpoint list was app-specific, and it compounded defect 3.**\\n//! `/api/weather?lat=-17.83&lon=31.05` is hardcoded into a component anyone can\\n//! install. In any app that is not the weather app, that probe fails every time —\\n//! one failure out of two — which defect 3 then classifies as a systemic outage.\\n//! Installed anywhere else, this component reported a systemic outage on every\\n//! single diagnosis. There is no default probe set here: an empty list means\\n//! nothing was probed, and [`BlastRadius::Unknown`] says so.\\n//!\\n//! **5. The report was written to the console on every diagnosis.** A\\n//! `console.warn` with `JSON.stringify(report, null, 2)`, unconditionally, in a\\n//! component that \\\"runs in production\\\" by its own header — carrying an error\\n//! message that is attacker-influenced whenever user input reaches an exception.\\n//! This core returns the report; where it goes is the host's.\\n//!\\n//! # Randomness is supplied, as everywhere in this node\\n//!\\n//! [`should_inject_error`] takes the draw rather than making it — not for a\\n//! security reason, but because a core with no I/O has no entropy source, and a\\n//! caller that supplies the draw can test the boundary rather than the odds.\\n\\nuse std::collections::BTreeSet;\\n\\n/// How chaos is configured.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct ChaosConfig {\\n /// Whether to inject anything at all.\\n pub enabled: bool,\\n /// Chance of an injected error, per render.\\n pub error_probability: f64,\\n /// Chance of injected latency, per fetch.\\n pub latency_probability: f64,\\n /// The ceiling on injected latency, in milliseconds.\\n pub max_latency_ms: u32,\\n /// Which helix nodes to target. Empty means all.\\n ///\\n /// The `.ts` declares this and never reads it, so narrowing chaos to one node\\n /// had no effect. Uncapped — node numbers are labels (CLAUDE.md §9).\\n pub target_nodes: BTreeSet<u32>,\\n /// A remote flag key the HOST resolves.\\n ///\\n /// This core never fetches it; a flag lookup is I/O. The host resolves the key\\n /// and reflects the answer in [`ChaosConfig::enabled`]. The `.ts` reads it\\n /// nowhere at all, which is not the same thing.\\n pub feature_flag: Option<String>,\\n}\\n\\nimpl Default for ChaosConfig {\\n /// The `.ts` defaults: off, 0.1% errors, 0.5% latency, 3s ceiling, all nodes.\\n fn default() -> Self {\\n Self {\\n enabled: false,\\n error_probability: 0.001,\\n latency_probability: 0.005,\\n max_latency_ms: 3000,\\n target_nodes: BTreeSet::new(),\\n feature_flag: None,\\n }\\n }\\n}\\n\\nimpl ChaosConfig {\\n /// Whether this node is in scope.\\n ///\\n /// An empty [`ChaosConfig::target_nodes`] means every node, which is what the\\n /// field's own documentation says and what the `.ts` never implemented.\\n #[must_use]\\n pub fn targets(&self, node: u32) -> bool {\\n self.target_nodes.is_empty() || self.target_nodes.contains(&node)\\n }\\n}\\n\\n/// Whether to inject an error into a component on this node.\\n///\\n/// `draw` is a value in `[0, 1)` from the host.\\n#[must_use]\\npub fn should_inject_error(config: &ChaosConfig, node: u32, draw: f64) -> bool {\\n config.enabled && config.targets(node) && draw < config.error_probability\\n}\\n\\n/// Whether to inject latency into a request from this node.\\n#[must_use]\\npub fn should_inject_latency(config: &ChaosConfig, node: u32, draw: f64) -> bool {\\n config.enabled && config.targets(node) && draw < config.latency_probability\\n}\\n\\n/// How much latency to inject.\\n///\\n/// Returns 0 when chaos is disabled. The `.ts` computes a delay regardless, so a\\n/// caller reaching for it directly slowed a system with chaos switched off.\\n#[must_use]\\npub fn injected_latency_ms(config: &ChaosConfig, draw: f64) -> u32 {\\n if !config.enabled {\\n return 0;\\n }\\n let clamped = draw.clamp(0.0, 1.0);\\n (clamped * f64::from(config.max_latency_ms)).floor() as u32\\n}\\n\\n/// The message an injected error carries.\\n///\\n/// The prefix is load-bearing: an injected failure that reads like a real one\\n/// wastes an on-call hour, and this string is what tells them apart in a log.\\n#[must_use]\\npub fn injected_error_message(node: u32, component: &str) -> String {\\n format!(\\\"[mzizi:chaos] Injected error in Node {node} component \\\\\\\"{component}\\\\\\\"\\\")\\n}\\n\\n/// How a probe answered.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum ProbeStatus {\\n /// Answered, and well.\\n Healthy,\\n /// Answered, badly.\\n Degraded,\\n /// Did not answer in time.\\n Timeout,\\n /// Did not answer.\\n Error,\\n}\\n\\nimpl ProbeStatus {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Healthy => \\\"healthy\\\",\\n Self::Degraded => \\\"degraded\\\",\\n Self::Timeout => \\\"timeout\\\",\\n Self::Error => \\\"error\\\",\\n }\\n }\\n\\n /// Whether this counts toward the blast radius.\\n ///\\n /// `degraded` does NOT, matching the `.ts`: a slow-but-answering dependency is\\n /// not the same signal as an unreachable one, and folding the two together\\n /// would make every busy afternoon look like an outage.\\n #[must_use]\\n pub const fn is_down(self) -> bool {\\n matches!(self, Self::Timeout | Self::Error)\\n }\\n}\\n\\n/// What one probe found.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct ProbeResult {\\n /// What was probed.\\n pub target: String,\\n /// How it answered.\\n pub status: ProbeStatus,\\n /// How long it took, in milliseconds.\\n pub latency_ms: f64,\\n /// What went wrong, when something did.\\n pub error: Option<String>,\\n}\\n\\n/// How far a failure reaches.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum BlastRadius {\\n /// Nothing else is down.\\n Isolated,\\n /// Some dependencies are down.\\n Partial,\\n /// Most or all are.\\n Systemic,\\n /// Nothing was probed, so nothing is known.\\n ///\\n /// The `.ts` has no such case: zero probes gives `errorCount === 0`, which is\\n /// `isolated` — \\\"component-level issue, retry should resolve\\\", asserted with\\n /// no evidence whatsoever.\\n Unknown,\\n}\\n\\nimpl BlastRadius {\\n /// The wire spelling. `unknown` has no `.ts` counterpart.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Isolated => \\\"isolated\\\",\\n Self::Partial => \\\"partial\\\",\\n Self::Systemic => \\\"systemic\\\",\\n Self::Unknown => \\\"unknown\\\",\\n }\\n }\\n\\n /// What to do about it.\\n #[must_use]\\n pub const fn recommendation(self) -> &'static str {\\n match self {\\n Self::Isolated => \\\"Component-level issue. Retry should resolve.\\\",\\n Self::Partial => \\\"Multiple services affected. Check infrastructure.\\\",\\n Self::Systemic => \\\"Systemic outage. Activate incident response.\\\",\\n Self::Unknown => \\\"No dependencies were probed. Blast radius unknown.\\\",\\n }\\n }\\n}\\n\\n/// The fraction of probes that must be down, EXCLUSIVELY, for a failure to be\\n/// systemic. More than half, not half.\\n///\\n/// The strictness is the whole fix and it is easy to get wrong twice: `>= 0.5`\\n/// leaves `partial` exactly as unreachable with two probes as the `.ts`'s\\n/// `< length / 2` did — 0 down is isolated, 1 is systemic, 2 is systemic. This\\n/// author wrote `>=` first and the test caught it. With `>`, two probes reach all\\n/// three bands, which is what the classifier claims to offer.\\npub const SYSTEMIC_FRACTION: f64 = 0.5;\\n\\n/// Classify a set of probe results.\\n///\\n/// Nothing down is [`BlastRadius::Isolated`]; MORE than [`SYSTEMIC_FRACTION`] down\\n/// is [`BlastRadius::Systemic`]; anything between is [`BlastRadius::Partial`].\\n///\\n/// The `.ts` writes `errorCount < probes.length / 2`, which for its own default of\\n/// two probes makes `partial` unreachable — one failure out of two lands on\\n/// `systemic` and recommends activating incident response.\\n#[must_use]\\npub fn classify_blast_radius(probes: &[ProbeResult]) -> BlastRadius {\\n if probes.is_empty() {\\n return BlastRadius::Unknown;\\n }\\n let down = probes.iter().filter(|p| p.status.is_down()).count();\\n if down == 0 {\\n return BlastRadius::Isolated;\\n }\\n if (down as f64 / probes.len() as f64) > SYSTEMIC_FRACTION {\\n BlastRadius::Systemic\\n } else {\\n BlastRadius::Partial\\n }\\n}\\n\\n/// What a diagnosis concluded.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct DiagnosticReport {\\n /// When, milliseconds since the Unix epoch.\\n pub at_ms: f64,\\n /// Which component failed first.\\n pub trigger_component: String,\\n /// What it said.\\n pub trigger_error: String,\\n /// What the dependencies said.\\n pub probes: Vec<ProbeResult>,\\n /// How far it reaches.\\n pub blast_radius: BlastRadius,\\n /// What to do.\\n pub recommendation: String,\\n}\\n\\n/// Turn probe results into a report.\\n///\\n/// There is no default endpoint list, deliberately. The `.ts` ships\\n/// `/api/weather?lat=-17.83&lon=31.05` as a default in a component anyone can\\n/// install, so in any app that is not the weather app that probe fails every time\\n/// — one failure out of two, which its classifier then calls a systemic outage.\\n/// Installed anywhere else, it reported a systemic outage on every diagnosis. The\\n/// host names what to probe; probing nothing answers [`BlastRadius::Unknown`].\\n///\\n/// The report is RETURNED, never logged. The `.ts` writes it to the console on\\n/// every diagnosis, in a component whose own header says it runs in production,\\n/// carrying an error message that is attacker-influenced whenever user input\\n/// reaches an exception.\\n#[must_use]\\npub fn diagnose(\\n trigger_component: impl Into<String>,\\n trigger_error: impl Into<String>,\\n probes: Vec<ProbeResult>,\\n at_ms: f64,\\n) -> DiagnosticReport {\\n let blast_radius = classify_blast_radius(&probes);\\n DiagnosticReport {\\n at_ms,\\n trigger_component: trigger_component.into(),\\n trigger_error: trigger_error.into(),\\n probes,\\n blast_radius,\\n recommendation: blast_radius.recommendation().to_owned(),\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1205
|
+
"mzizi-command-palette": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-command-palette\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Global command palette (Cmd+K / Ctrl+K). Search across mini-apps, navigate to pages, run actions. Competitors: Vercel, Linear, Raycast. Overlay with fuzzy search, recent items, and categorized results.\",\"crate\":{\"name\":\"mzizi-shell\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n7-shell/mzizi-command-palette.rs\",\"type\":\"registry:rust\",\"content\":\"//! NYUCHI COMMAND PALETTE — N7 shell, Dioxus.\\n//!\\n//! The Rust sibling of `mzizi-command-palette.tsx`: global search + actions, Cmd+K / Ctrl+K,\\n//! sharing its contract — same `data-slot`, same node-mineral colour map, same grouping.\\n\\nuse dioxus::prelude::*;\\n\\n/// A single result/action row. Selection is dispatched by `id` through\\n/// `MziziCommandPaletteProps::on_select` rather than a per-item closure,\\n/// so the item list stays plain data (Clone/PartialEq, easy to test).\\n#[derive(Clone, Debug, PartialEq)]\\npub struct CommandItem {\\n /// Unique identifier, passed back through `on_select`.\\n pub id: String,\\n /// Display label.\\n pub label: String,\\n /// Optional secondary text shown under the label.\\n pub description: Option<String>,\\n /// Result group; items with no category fall under \\\"Results\\\".\\n pub category: Option<String>,\\n /// Optional keyboard shortcut hint, shown when `node` is absent.\\n pub shortcut: Option<String>,\\n /// Architecture node (1-12) — renders as a mineral-coloured N-chip.\\n pub node: Option<u8>,\\n}\\n\\n/// Node -> mineral Tailwind class. Static strings only (never constructed), per the\\n/// design-system styling rule — Tailwind's content scanner needs the literal class.\\npub fn node_mineral_class(node: u8) -> &'static str {\\n match node {\\n 1 => \\\"text-terracotta\\\",\\n 2 => \\\"text-cobalt\\\",\\n 3 => \\\"text-tanzanite\\\",\\n 4 => \\\"text-gold\\\",\\n 5 => \\\"text-malachite\\\",\\n 6 => \\\"text-cobalt\\\",\\n 7 => \\\"text-gold\\\",\\n 8 => \\\"text-sodalite\\\",\\n 9 => \\\"text-copper\\\",\\n 10 => \\\"text-sodalite\\\",\\n _ => \\\"text-muted-foreground\\\",\\n }\\n}\\n\\n/// Groups items by category, first-seen order — matching the .tsx's\\n/// `acc[cat] ||= []` reduce, whose insertion order a `BTreeMap` would have\\n/// silently re-sorted alphabetically and changed the on-screen order.\\npub fn group_by_category(items: &[CommandItem]) -> Vec<(String, Vec<CommandItem>)> {\\n let mut groups: Vec<(String, Vec<CommandItem>)> = Vec::new();\\n for item in items {\\n let cat = item\\n .category\\n .clone()\\n .unwrap_or_else(|| \\\"Results\\\".to_string());\\n match groups.iter_mut().find(|(c, _)| *c == cat) {\\n Some((_, list)) => list.push(item.clone()),\\n None => groups.push((cat, vec![item.clone()])),\\n }\\n }\\n groups\\n}\\n\\n/// Cmd+K / Ctrl+K toggles the palette open or closed.\\npub fn should_toggle(key: &str, meta_or_ctrl: bool) -> bool {\\n meta_or_ctrl && key == \\\"k\\\"\\n}\\n\\n/// Escape closes the palette only while it is open.\\npub fn should_close_on_escape(key: &str, open: bool) -> bool {\\n open && key == \\\"Escape\\\"\\n}\\n\\n/// Props for [`MziziCommandPalette`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziCommandPaletteProps {\\n /// Whether the palette is currently shown.\\n pub open: bool,\\n /// Search results for the current query.\\n pub items: Vec<CommandItem>,\\n /// Shown instead of `items` while the query is empty.\\n pub recent_items: Vec<CommandItem>,\\n /// Placeholder text for the search input.\\n #[props(default = \\\"Search or type a command...\\\".to_string())]\\n pub placeholder: String,\\n /// The current search query, controlled by the host.\\n pub query: String,\\n /// Whether a search is in flight.\\n pub loading: bool,\\n /// Fired as the query text changes.\\n #[props(default)]\\n pub on_query_change: EventHandler<String>,\\n /// Fired with an item's `id` when it is chosen.\\n #[props(default)]\\n pub on_select: EventHandler<String>,\\n /// Fired to open or close the palette (backdrop click, selection, Escape).\\n #[props(default)]\\n pub on_open_change: EventHandler<bool>,\\n}\\n\\n/// Global search / command dialog, Cmd+K-style.\\n#[component]\\npub fn MziziCommandPalette(props: MziziCommandPaletteProps) -> Element {\\n if !props.open {\\n return rsx! {};\\n }\\n let grouped = group_by_category(&props.items);\\n\\n rsx! {\\n div {\\n class: \\\"fixed inset-0 z-50 bg-black/50\\\",\\n \\\"aria-hidden\\\": \\\"true\\\",\\n onclick: move |_| props.on_open_change.call(false),\\n }\\n div {\\n \\\"data-slot\\\": \\\"mzizi-command-palette\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-command-palette\\\",\\n role: \\\"dialog\\\",\\n \\\"aria-label\\\": \\\"Command palette\\\",\\n \\\"aria-modal\\\": \\\"true\\\",\\n class: \\\"fixed inset-x-4 top-[15%] z-50 mx-auto max-w-lg overflow-hidden rounded-xl border border-border bg-card shadow-2xl\\\",\\n\\n div { class: \\\"flex items-center border-b border-border px-4\\\",\\n input {\\n value: \\\"{props.query}\\\",\\n placeholder: \\\"{props.placeholder}\\\",\\n \\\"aria-label\\\": \\\"Search commands\\\",\\n class: \\\"flex-1 bg-transparent px-3 py-4 text-sm outline-none placeholder:text-muted-foreground\\\",\\n oninput: move |e| props.on_query_change.call(e.value()),\\n }\\n }\\n\\n div { class: \\\"max-h-[60vh] overflow-y-auto p-2\\\", role: \\\"listbox\\\",\\n if props.query.is_empty() && !props.recent_items.is_empty() {\\n div {\\n p { class: \\\"px-2 py-1.5 text-xs font-medium text-muted-foreground\\\", \\\"Recent\\\" }\\n for item in props.recent_items.iter() {\\n button {\\n key: \\\"{item.id}\\\",\\n role: \\\"option\\\",\\n class: \\\"flex min-h-[48px] w-full items-center gap-3 rounded-sm px-3 py-2 text-left text-sm transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none\\\",\\n onclick: {\\n let id = item.id.clone();\\n move |_| { props.on_select.call(id.clone()); props.on_open_change.call(false); }\\n },\\n \\\"{item.label}\\\"\\n }\\n }\\n }\\n }\\n for (cat, cat_items) in grouped.iter() {\\n div {\\n key: \\\"{cat}\\\",\\n p { class: \\\"px-2 py-1.5 text-xs font-medium text-muted-foreground\\\", \\\"{cat}\\\" }\\n for item in cat_items.iter() {\\n button {\\n key: \\\"{item.id}\\\",\\n role: \\\"option\\\",\\n class: \\\"flex min-h-[48px] w-full items-center justify-between gap-3 rounded-sm px-3 py-2 text-left text-sm transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none\\\",\\n onclick: {\\n let id = item.id.clone();\\n move |_| { props.on_select.call(id.clone()); props.on_open_change.call(false); }\\n },\\n \\\"{item.label}\\\"\\n }\\n }\\n }\\n }\\n if !props.query.is_empty() && props.items.is_empty() && !props.loading {\\n p { class: \\\"py-8 text-center text-sm text-muted-foreground\\\", \\\"No results found\\\" }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n fn item(id: &str, category: Option<&str>) -> CommandItem {\\n CommandItem {\\n id: id.into(),\\n label: id.into(),\\n description: None,\\n category: category.map(str::to_string),\\n shortcut: None,\\n node: None,\\n }\\n }\\n\\n #[test]\\n fn unknown_node_falls_back_to_muted_not_a_wrong_colour() {\\n assert_eq!(node_mineral_class(11), \\\"text-muted-foreground\\\");\\n assert_eq!(node_mineral_class(1), \\\"text-terracotta\\\");\\n }\\n\\n #[test]\\n fn grouping_preserves_first_seen_category_order() {\\n let items = vec![\\n item(\\\"a\\\", Some(\\\"Zebra\\\")),\\n item(\\\"b\\\", Some(\\\"Apple\\\")),\\n item(\\\"c\\\", Some(\\\"Zebra\\\")),\\n ];\\n let grouped = group_by_category(&items);\\n let order: Vec<&str> = grouped.iter().map(|(c, _)| c.as_str()).collect();\\n // \\\"Zebra\\\" appeared first in the input, so it must stay first — a BTreeMap\\n // would have alphabetized this to Apple, Zebra and silently reordered the UI.\\n assert_eq!(order, vec![\\\"Zebra\\\", \\\"Apple\\\"]);\\n assert_eq!(grouped[0].1.len(), 2);\\n }\\n\\n #[test]\\n fn uncategorised_items_group_under_results() {\\n let items = vec![item(\\\"a\\\", None)];\\n let grouped = group_by_category(&items);\\n assert_eq!(grouped[0].0, \\\"Results\\\");\\n }\\n\\n #[test]\\n fn ctrl_or_meta_k_toggles() {\\n assert!(should_toggle(\\\"k\\\", true));\\n assert!(!should_toggle(\\\"k\\\", false));\\n assert!(!should_toggle(\\\"j\\\", true));\\n }\\n\\n #[test]\\n fn escape_only_closes_while_open() {\\n assert!(should_close_on_escape(\\\"Escape\\\", true));\\n assert!(!should_close_on_escape(\\\"Escape\\\", false));\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1206
|
+
"mzizi-conformity-check": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-conformity-check\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"Conformity monkey — verifies deployed components match the design registry. Scans the DOM for data-slot/data-portal attributes and cross-references with the component database. Flags: unregistered components, outdated versions, missing backlinks, components using deprecated patterns. Inspired by Netflix Conformity Monkey.\",\"crate\":{\"name\":\"mzizi-assurance\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n8-assurance/mzizi-conformity-check.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N8 assurance — does production match the registry?\\n//!\\n//! The Rust implementation of `mzizi-conformity-check` (a Conformity Monkey for\\n//! the component registry).\\n//!\\n//! # What this owns\\n//!\\n//! The rules and the score. Not the DOM walk: the host collects what it sees and\\n//! hands over a list of [`ObservedElement`], which is the only part that needs a\\n//! browser.\\n//!\\n//! That split is what makes the check runnable somewhere it never was. The `.ts`\\n//! calls `document.querySelectorAll` directly, so conformity could only ever be\\n//! assessed inside a live page — never against server-rendered HTML in CI, and\\n//! never from the Worker that is supposed to notice when a deploy stops\\n//! conforming.\\n//!\\n//! # Three defects in the TypeScript, fixed here\\n//!\\n//! **1. `onViolation` fired for one violation type out of four.** The\\n//! `unregistered` branch builds its violation object twice — once to push, once\\n//! to hand to the callback — and every other branch only pushes. So a consumer\\n//! wiring `onViolation` to an alerting path saw unregistered components and\\n//! silently never saw a deprecated one, a missing portal link or a missing\\n//! accessible name. Structurally impossible here: [`check_conformity`] returns\\n//! every violation and the host dispatches, so there is no second code path to\\n//! forget.\\n//!\\n//! **2. The score was wrong whenever a component name repeated.** Conformance was\\n//! counted with `violations.filter(v => v.componentName === slot).length === 0`,\\n//! which asks \\\"has *any* element with this slot name had a violation\\\" — so one\\n//! bad button marks every other button on the page non-conformant. A page with\\n//! twenty conformant buttons and one broken one scored zero for all twenty-one.\\n//! Counted per element here.\\n//!\\n//! It was also O(n²): a full scan of the violation list for every element.\\n//!\\n//! **3. An unregistered component skipped every other check.** The `.ts` returns\\n//! early, so an unregistered component that is *also* deprecated and *also*\\n//! missing an accessible name reports one violation instead of three. Being\\n//! absent from the registry does not make the other findings untrue, and the\\n//! aria one is a real accessibility defect either way.\\n//!\\n//! **4. The `missing_aria` rule could never fire for a `role=\\\"button\\\"` element.**\\n//! The `.ts` computes `const ariaLabel = el.getAttribute(\\\"aria-label\\\") ||\\n//! el.getAttribute(\\\"role\\\")` and then refuses to report when `ariaLabel` is truthy\\n//! — so any element carrying `role=\\\"button\\\"` supplies its own \\\"name\\\" and is\\n//! exempted by the very attribute that brought it into the rule's scope. The rule\\n//! was widened past `<button>` specifically to catch those elements and then\\n//! excluded all of them; only a literal `<button>` with no `role`, no `aria-label`\\n//! and no text could ever be reported.\\n//!\\n//! A role is not a name. `mzizi-a11y-audit` had the correct definition all along\\n//! — `aria-label`, `aria-labelledby`, or text — and two N8 components disagreeing\\n//! about what an accessible name IS meant the same button was a violation on one\\n//! surface and a pass on the other. [`ObservedElement::has_accessible_name`] now\\n//! matches the audit, `aria-labelledby` included.\\n//!\\n//! # Two variants that never had a producer\\n//!\\n//! `version_mismatch` and `missing_slot` are declared in the `.ts` union and\\n//! nothing emits either. They are kept because both describe real conditions a\\n//! host can detect — a rendered component whose version differs from the\\n//! registry's, and a registry component rendered with no `data-slot` at all —\\n//! and a union that can express a finding is better than one that cannot. What\\n//! is not kept is any pretence that this module produces them.\\n\\nuse std::collections::BTreeSet;\\n\\n/// What kind of conformity failure.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum ViolationType {\\n /// Rendered but absent from the registry.\\n Unregistered,\\n /// Missing the `data-portal` backlink.\\n MissingPortal,\\n /// Registered, but deprecated.\\n Deprecated,\\n /// Rendered version differs from the registry's. Never produced here.\\n VersionMismatch,\\n /// An interactive element with no accessible name.\\n MissingAria,\\n /// A registry component rendered without `data-slot`. Never produced here.\\n MissingSlot,\\n}\\n\\nimpl ViolationType {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Unregistered => \\\"unregistered\\\",\\n Self::MissingPortal => \\\"missing_portal\\\",\\n Self::Deprecated => \\\"deprecated\\\",\\n Self::VersionMismatch => \\\"version_mismatch\\\",\\n Self::MissingAria => \\\"missing_aria\\\",\\n Self::MissingSlot => \\\"missing_slot\\\",\\n }\\n }\\n}\\n\\n/// How much a violation matters.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum ViolationSeverity {\\n /// Worth knowing.\\n Info,\\n /// Worth fixing.\\n Warning,\\n /// Worth blocking on.\\n Error,\\n}\\n\\nimpl ViolationSeverity {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Info => \\\"info\\\",\\n Self::Warning => \\\"warning\\\",\\n Self::Error => \\\"error\\\",\\n }\\n }\\n}\\n\\n/// What the host saw on the page.\\n///\\n/// One of these per element carrying a `data-slot`. Gathering them is the only\\n/// step that needs a browser; everything after is arithmetic.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct ObservedElement {\\n /// The `data-slot` value.\\n pub slot: String,\\n /// The element's tag name, e.g. `BUTTON`.\\n pub tag_name: String,\\n /// The `data-portal` backlink, when present.\\n pub portal_url: Option<String>,\\n /// The `aria-label`, when present.\\n pub aria_label: Option<String>,\\n /// The `aria-labelledby`, when present.\\n pub aria_labelledby: Option<String>,\\n /// The `role`, when present. Decides whether the element is INTERACTIVE; it\\n /// is not a source of an accessible name.\\n pub role: Option<String>,\\n /// Whether the element has any non-whitespace text.\\n pub has_text: bool,\\n}\\n\\nimpl ObservedElement {\\n /// Whether this element is interactive in the sense the aria rule means.\\n #[must_use]\\n pub fn is_interactive(&self) -> bool {\\n self.tag_name.eq_ignore_ascii_case(\\\"button\\\") || self.role.as_deref() == Some(\\\"button\\\")\\n }\\n\\n /// Whether a screen reader would announce a name for it.\\n ///\\n /// `role` is deliberately absent. Counting it — as the `.ts` does — makes the\\n /// rule unfireable for the `role=\\\"button\\\"` elements it exists to cover.\\n #[must_use]\\n pub fn has_accessible_name(&self) -> bool {\\n self.aria_label.is_some() || self.aria_labelledby.is_some() || self.has_text\\n }\\n}\\n\\n/// One finding.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct ConformityViolation {\\n /// What kind.\\n pub violation_type: ViolationType,\\n /// Which component.\\n pub component_name: String,\\n /// The element's tag.\\n pub element: String,\\n /// A selector that finds it again.\\n pub selector: String,\\n /// What is wrong, in words.\\n pub message: String,\\n /// Where the component's documentation is.\\n pub portal_url: Option<String>,\\n /// How much it matters.\\n pub severity: ViolationSeverity,\\n}\\n\\n/// What a whole page scored.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct ConformityReport {\\n /// The path checked.\\n pub url: String,\\n /// How many slotted elements were seen.\\n pub total_components: usize,\\n /// How many had no violations of their own.\\n pub conformant: usize,\\n /// Every finding.\\n pub violations: Vec<ConformityViolation>,\\n /// Percentage conformant, 0-100.\\n pub score: u32,\\n}\\n\\n/// A CSS selector for a slot value, with quotes escaped.\\n///\\n/// A slot containing `\\\"` would otherwise produce a selector that does not parse\\n/// — and the selector's whole job is to let somebody find the element again.\\n#[must_use]\\npub fn selector_for(slot: &str) -> String {\\n format!(\\n \\\"[data-slot=\\\\\\\"{}\\\\\\\"]\\\",\\n slot.replace('\\\\\\\\', \\\"\\\\\\\\\\\\\\\\\\\").replace('\\\"', \\\"\\\\\\\\\\\\\\\"\\\")\\n )\\n}\\n\\n/// Check one element against the registry.\\n///\\n/// Returns every violation it has, not the first. An unregistered component that\\n/// is also missing its accessible name has two problems, and the second is a real\\n/// accessibility defect regardless of the first.\\n#[must_use]\\npub fn check_element(\\n element: &ObservedElement,\\n registry: Option<&BTreeSet<String>>,\\n deprecated: &BTreeSet<String>,\\n) -> Vec<ConformityViolation> {\\n let mut violations = Vec::new();\\n let selector = selector_for(&element.slot);\\n\\n let base = |violation_type: ViolationType,\\n message: String,\\n severity: ViolationSeverity,\\n portal_url: Option<String>| ConformityViolation {\\n violation_type,\\n component_name: element.slot.clone(),\\n element: element.tag_name.clone(),\\n selector: selector.clone(),\\n message,\\n portal_url,\\n severity,\\n };\\n\\n if let Some(registry) = registry\\n && !registry.contains(&element.slot)\\n {\\n violations.push(base(\\n ViolationType::Unregistered,\\n format!(\\n \\\"Component \\\\\\\"{}\\\\\\\" is not in the design registry\\\",\\n element.slot\\n ),\\n ViolationSeverity::Warning,\\n None,\\n ));\\n }\\n\\n if element.portal_url.is_none() {\\n violations.push(base(\\n ViolationType::MissingPortal,\\n format!(\\n \\\"Component \\\\\\\"{}\\\\\\\" missing data-portal attribute\\\",\\n element.slot\\n ),\\n ViolationSeverity::Info,\\n None,\\n ));\\n }\\n\\n if deprecated.contains(&element.slot) {\\n violations.push(base(\\n ViolationType::Deprecated,\\n format!(\\\"Component \\\\\\\"{}\\\\\\\" is deprecated\\\", element.slot),\\n ViolationSeverity::Error,\\n element.portal_url.clone(),\\n ));\\n }\\n\\n if element.is_interactive() && !element.has_accessible_name() {\\n violations.push(base(\\n ViolationType::MissingAria,\\n format!(\\n \\\"Interactive element in \\\\\\\"{}\\\\\\\" missing accessible name\\\",\\n element.slot\\n ),\\n ViolationSeverity::Warning,\\n element.portal_url.clone(),\\n ));\\n }\\n\\n violations\\n}\\n\\n/// Check a whole page.\\n///\\n/// Conformance is counted PER ELEMENT. The `.ts` counts per slot NAME, so one\\n/// broken button marks every other button non-conformant — a page with twenty\\n/// good buttons and one bad one scored zero for all twenty-one.\\n#[must_use]\\npub fn check_conformity(\\n url: impl Into<String>,\\n elements: &[ObservedElement],\\n registry: Option<&BTreeSet<String>>,\\n deprecated: &BTreeSet<String>,\\n) -> ConformityReport {\\n let mut violations = Vec::new();\\n let mut conformant = 0usize;\\n\\n for element in elements {\\n let found = check_element(element, registry, deprecated);\\n if found.is_empty() {\\n conformant += 1;\\n }\\n violations.extend(found);\\n }\\n\\n let total = elements.len();\\n ConformityReport {\\n url: url.into(),\\n total_components: total,\\n conformant,\\n violations,\\n // An empty page is fully conformant rather than a division by zero. It\\n // has nothing wrong with it, which is different from being untested —\\n // `total_components` says which.\\n score: if total == 0 {\\n 100\\n } else {\\n ((conformant as f64 / total as f64) * 100.0).round() as u32\\n },\\n }\\n}\\n\\n/// The worst severity present, for a single go/no-go answer.\\n#[must_use]\\npub fn worst_severity(violations: &[ConformityViolation]) -> Option<ViolationSeverity> {\\n violations.iter().map(|v| v.severity).max()\\n}\\n\"}],\"contract\":null}",
|
|
1207
|
+
"mzizi-connectivity-bar": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-connectivity-bar\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Network connectivity status bar. Shows connection state: online, syncing, cached, offline. Auto-hides when online. Uses L1 connection tokens and L5 offline-banner pattern. Placed at top of app by root layout.\",\"crate\":{\"name\":\"mzizi-shell\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n7-shell/mzizi-connectivity-bar.rs\",\"type\":\"registry:rust\",\"content\":\"//! NYUCHI CONNECTIVITY BAR — N7 shell, Dioxus.\\n//!\\n//! The Rust sibling of `mzizi-connectivity-bar.tsx`: a status strip announcing the app's\\n//! network state, sharing its contract — same `data-slot`, same four states, same colours.\\n//!\\n//! # The retry control is below the published touch floor\\n//!\\n//! The `.tsx` ships `min-h-[44px]` on the retry button. N10's own `mzizi-ai-context` states\\n//! the rule as \\\"Touch targets: 48px minimum\\\" — 44 is Apple's number, not this system's, and\\n//! this is the fourth component in this build-out with the identical shortfall\\n//! (`mzizi-docs-engine`'s sidebar was the first two). Raised to `min-h-[48px]` here; nothing\\n//! else about the control changes.\\n//!\\n//! **The `.tsx` sibling still has this.**\\n//!\\n//! # The harness is a parameter, not a dependency\\n//!\\n//! The `.tsx` calls `useMziziHarness(\\\"connectivity-bar\\\")` for `log.warn`. That hook is\\n//! itself an N3 component (`mzizi-harness.tsx`) with no Rust port yet, and this component\\n//! should not block on one existing — nor should porting the harness later require touching\\n//! every N7 component that logs through it. [`ConnectivityBarProps::on_state_change`] is a\\n//! plain callback the host wires to whatever logger it has.\\n//!\\n//! # Auto-hide is host-owned, not timer-owned\\n//!\\n//! The `.tsx` starts a `setTimeout` internally and hides itself when it fires. Reproducing\\n//! that here would pull a timer dependency that differs by target — `gloo-timers` on wasm,\\n//! something else natively — into a crate that otherwise has none, to save the host four\\n//! lines. [`ConnectivityBarProps::visible`] is a plain controlled prop instead: the host\\n//! decides when the bar shows, exactly as it already decides `state`. `auto_hide_delay_ms`\\n//! is kept as data the host can act on, not a timer this component runs itself.\\n\\nuse dioxus::prelude::*;\\n\\n/// Network state this bar reports.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\\npub enum ConnectionState {\\n /// Connected. Auto-hides after [`ConnectivityBarProps::auto_hide_delay_ms`] by default.\\n #[default]\\n Online,\\n /// A sync is in flight.\\n Syncing,\\n /// Serving stale data because the network is unavailable.\\n Cached,\\n /// No connection at all.\\n Offline,\\n}\\n\\nimpl ConnectionState {\\n /// Default label, matching the `.tsx` `STATE_CONFIG`.\\n #[must_use]\\n pub const fn default_label(self) -> &'static str {\\n match self {\\n Self::Online => \\\"Connected\\\",\\n Self::Syncing => \\\"Syncing...\\\",\\n Self::Cached => \\\"Using cached data\\\",\\n Self::Offline => \\\"No connection\\\",\\n }\\n }\\n\\n /// CSS colour expression, byte-identical to the `.tsx`.\\n #[must_use]\\n pub const fn colour(self) -> &'static str {\\n match self {\\n Self::Online => \\\"var(--connection-online, var(--status-success, #64FFDA))\\\",\\n Self::Syncing => \\\"var(--connection-syncing, var(--status-info, #00B0FF))\\\",\\n Self::Cached => \\\"var(--connection-cached, var(--status-warning, #FFD740))\\\",\\n Self::Offline => \\\"var(--connection-offline, var(--status-error, #FF5252))\\\",\\n }\\n }\\n\\n /// `data-state` value.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Online => \\\"online\\\",\\n Self::Syncing => \\\"syncing\\\",\\n Self::Cached => \\\"cached\\\",\\n Self::Offline => \\\"offline\\\",\\n }\\n }\\n}\\n\\nconst ROOT: &str = \\\"flex items-center justify-center gap-2 px-4 py-1.5 text-xs font-medium text-white transition-all\\\";\\nconst SPINNER: &str = \\\"size-3 animate-spin rounded-full border-2 border-white/30 border-t-white\\\";\\n/// The `.tsx` says `min-h-[44px]`. See the module docs.\\nconst RETRY: &str = \\\"ml-2 min-h-[48px] underline hover:no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white\\\";\\n\\n/// Join a base class string with a consumer's extra classes.\\nfn join(base: &str, extra: &str) -> String {\\n if extra.is_empty() {\\n base.to_owned()\\n } else {\\n format!(\\\"{base} {extra}\\\")\\n }\\n}\\n\\n/// Props for [`ConnectivityBar`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct ConnectivityBarProps {\\n /// Current connection state.\\n #[props(default)]\\n pub state: ConnectionState,\\n /// Overrides the state's default label.\\n #[props(default)]\\n pub message: Option<String>,\\n /// Whether the bar is shown right now. The host owns the auto-hide timer — see the\\n /// module docs — and flips this after `auto_hide_delay_ms` when it wants the `.tsx`'s\\n /// behaviour.\\n #[props(default = true)]\\n pub visible: bool,\\n /// Suggested auto-hide delay in milliseconds, for a host that wants to reproduce the\\n /// `.tsx` timing. Not enforced by this component.\\n #[props(default = 2000)]\\n pub auto_hide_delay_ms: u32,\\n /// Called on every non-online state, in place of the `.tsx`'s harness `log.warn`.\\n #[props(default)]\\n pub on_state_change: Option<EventHandler<ConnectionState>>,\\n /// Called when the retry control is pressed. Retry renders only when this is set AND\\n /// the state is [`ConnectionState::Offline`], matching the `.tsx`.\\n #[props(default)]\\n pub on_retry: Option<EventHandler<()>>,\\n /// Extra classes, merged onto the root.\\n #[props(default)]\\n pub class: String,\\n /// Any other root attributes.\\n #[props(extends = GlobalAttributes)]\\n pub attributes: Vec<Attribute>,\\n}\\n\\n/// A status strip announcing the app's network state.\\n#[component]\\npub fn ConnectivityBar(props: ConnectivityBarProps) -> Element {\\n let state = props.state;\\n\\n use_effect(move || {\\n if state != ConnectionState::Online {\\n if let Some(handler) = props.on_state_change {\\n handler.call(state);\\n }\\n }\\n });\\n\\n if !props.visible {\\n return rsx! {};\\n }\\n\\n let label = props\\n .message\\n .clone()\\n .unwrap_or_else(|| state.default_label().to_owned());\\n let colour = state.colour();\\n let style = format!(\\\"background-color: {colour};\\\");\\n\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-connectivity-bar\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-connectivity-bar\\\",\\n \\\"data-state\\\": state.as_str(),\\n role: \\\"status\\\",\\n \\\"aria-live\\\": \\\"polite\\\",\\n class: join(ROOT, &props.class),\\n style: \\\"{style}\\\",\\n ..props.attributes,\\n\\n if state == ConnectionState::Syncing {\\n div { class: SPINNER }\\n }\\n span { \\\"{label}\\\" }\\n if state == ConnectionState::Offline {\\n if let Some(on_retry) = props.on_retry {\\n button {\\n class: RETRY,\\n onclick: move |_| on_retry.call(()),\\n \\\"Retry\\\"\\n }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn labels_match_the_typescript() {\\n assert_eq!(ConnectionState::Online.default_label(), \\\"Connected\\\");\\n assert_eq!(ConnectionState::Syncing.default_label(), \\\"Syncing...\\\");\\n assert_eq!(ConnectionState::Cached.default_label(), \\\"Using cached data\\\");\\n assert_eq!(ConnectionState::Offline.default_label(), \\\"No connection\\\");\\n }\\n\\n #[test]\\n fn the_retry_control_meets_the_published_touch_floor() {\\n assert!(RETRY.contains(\\\"min-h-[48px]\\\"));\\n assert!(!RETRY.contains(\\\"44px\\\"));\\n }\\n\\n #[test]\\n fn data_state_values_are_lowercase_and_distinct() {\\n let all = [\\n ConnectionState::Online,\\n ConnectionState::Syncing,\\n ConnectionState::Cached,\\n ConnectionState::Offline,\\n ];\\n let mut seen = std::collections::HashSet::new();\\n for s in all {\\n assert_eq!(s.as_str(), s.as_str().to_lowercase());\\n assert!(seen.insert(s.as_str()));\\n }\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1208
|
+
"mzizi-cover-header": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-cover-header\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Cover image header with overlapping avatar or logo. Used by individual and org profile pages. Branded with mineral accents and verification badge slot. Extracted from individual-profile-page and org-profile-page.\",\"crate\":{\"name\":\"mzizi-brand\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n3-brand/mzizi-cover-header.rs\",\"type\":\"registry:rust\",\"content\":\"//! MZIZI COVER HEADER — N3 brand, Dioxus. A Mzizi Roots component.\\n//!\\n//! The Rust sibling of `mzizi-cover-header.tsx`: a cover image with an overlapping avatar,\\n//! a serif name, an optional badge and a trailing action. Same `data-slot`, same\\n//! `role=\\\"banner\\\"`, same three cover heights and two avatar shapes.\\n//!\\n//! # The harness's motion preference is a prop\\n//!\\n//! [`MziziCoverHeaderProps::prefers_reduced_motion`] replaces `useMziziHarness`'s\\n//! `motion.prefersReduced`, as `mzizi-update-prompt.rs` does in N7.\\n//!\\n//! # The cover URL is quoted\\n//!\\n//! The `.tsx` writes `url(${coverImage})` unquoted, so a URL containing a space or a `)`\\n//! breaks the declaration. [`cover_style`] quotes it and escapes the characters that would end\\n//! the string. **The `.tsx` sibling still writes it unquoted.**\\n\\nuse dioxus::prelude::*;\\n\\n/// The component's contract, in the clause grammar of the Mzizi language's `contract … end`\\n/// block (mzizi-dev/mzizi `design/RFC-0006-contracts.md`). `mzizi-brand`'s contract suite\\n/// evaluates every clause against this component's server-rendered markup and fails on any\\n/// clause it cannot evaluate.\\npub const CONTRACT: &str = r#\\\"contract\\n slot is \\\"mzizi-cover-header\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-cover-header\\\"\\n role is \\\"banner\\\"\\n cover_height.md class is \\\"h-32 sm:h-48\\\"\\n every avatar_shape class contains \\\"rounded-\\\"\\nend\\\"#;\\n\\n/// Cover height. Matches `HEIGHT_MAP` in the `.tsx`.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]\\npub enum CoverHeight {\\n /// Short cover.\\n Sm,\\n /// Default cover.\\n #[default]\\n Md,\\n /// Tall cover.\\n Lg,\\n}\\n\\nimpl CoverHeight {\\n /// Every height, for exhaustive checks.\\n pub const ALL: [Self; 3] = [Self::Sm, Self::Md, Self::Lg];\\n\\n /// The height classes.\\n #[must_use]\\n pub const fn classes(self) -> &'static str {\\n match self {\\n Self::Sm => \\\"h-24 sm:h-32\\\",\\n Self::Md => \\\"h-32 sm:h-48\\\",\\n Self::Lg => \\\"h-40 sm:h-56\\\",\\n }\\n }\\n\\n /// The value the `.tsx` accepts for `coverHeight`.\\n #[must_use]\\n pub const fn slug(self) -> &'static str {\\n match self {\\n Self::Sm => \\\"sm\\\",\\n Self::Md => \\\"md\\\",\\n Self::Lg => \\\"lg\\\",\\n }\\n }\\n}\\n\\n/// Avatar mask.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]\\npub enum AvatarShape {\\n /// Round avatar, for people.\\n #[default]\\n Circle,\\n /// Rounded square, for organisations.\\n Rounded,\\n}\\n\\nimpl AvatarShape {\\n /// Every shape, for exhaustive checks.\\n pub const ALL: [Self; 2] = [Self::Circle, Self::Rounded];\\n\\n /// The radius class.\\n #[must_use]\\n pub const fn classes(self) -> &'static str {\\n match self {\\n Self::Circle => \\\"rounded-full\\\",\\n Self::Rounded => \\\"rounded-[var(--radius-lg,14px)]\\\",\\n }\\n }\\n\\n /// The value the `.tsx` accepts for `avatarShape`.\\n #[must_use]\\n pub const fn slug(self) -> &'static str {\\n match self {\\n Self::Circle => \\\"circle\\\",\\n Self::Rounded => \\\"rounded\\\",\\n }\\n }\\n}\\n\\n/// The cover's inline style: a quoted, escaped `background-image`, or nothing.\\n#[must_use]\\npub fn cover_style(cover_image: Option<&str>) -> String {\\n match cover_image {\\n None => String::new(),\\n Some(url) => {\\n let escaped = url\\n .replace('\\\\\\\\', \\\"\\\\\\\\\\\\\\\\\\\")\\n .replace('\\\"', \\\"\\\\\\\\\\\\\\\"\\\")\\n .replace('\\\\n', \\\"\\\");\\n format!(\\\"background-image: url(\\\\\\\"{escaped}\\\\\\\");\\\")\\n }\\n }\\n}\\n\\n/// The entrance animation, empty when motion is reduced — the `.tsx`'s `animStyle`, with the\\n/// harness's 200ms decelerate curve.\\n#[must_use]\\npub fn entrance_style(prefers_reduced_motion: bool) -> &'static str {\\n if prefers_reduced_motion {\\n \\\"\\\"\\n } else {\\n \\\"animation: nyuchi-fade-slide-up 200ms cubic-bezier(0, 0, 0.2, 1) both;\\\"\\n }\\n}\\n\\n/// Props for [`MziziCoverHeader`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziCoverHeaderProps {\\n /// Cover image URL.\\n #[props(default)]\\n pub cover_image: Option<String>,\\n /// Avatar image URL.\\n #[props(default)]\\n pub avatar: Option<String>,\\n /// Avatar mask.\\n #[props(default)]\\n pub avatar_shape: AvatarShape,\\n /// Display name, in the serif face.\\n #[props(into)]\\n pub name: String,\\n /// Muted line under the name.\\n #[props(default)]\\n pub subtitle: Option<String>,\\n /// Badge after the name, e.g. a verified mark.\\n #[props(default)]\\n pub badge: Option<Element>,\\n /// Trailing action beside the avatar, e.g. a Follow button.\\n #[props(default)]\\n pub action: Option<Element>,\\n /// Cover height.\\n #[props(default)]\\n pub cover_height: CoverHeight,\\n /// Replaces the harness's `motion.prefersReduced`.\\n #[props(default)]\\n pub prefers_reduced_motion: bool,\\n /// Extra classes, appended last.\\n #[props(default)]\\n pub class: String,\\n}\\n\\n/// A cover image with an overlapping avatar and a name.\\n#[component]\\npub fn MziziCoverHeader(props: MziziCoverHeaderProps) -> Element {\\n let root = if props.class.is_empty() {\\n \\\"relative\\\".to_owned()\\n } else {\\n format!(\\\"relative {}\\\", props.class)\\n };\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-cover-header\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-cover-header\\\",\\n role: \\\"banner\\\",\\n style: entrance_style(props.prefers_reduced_motion),\\n class: \\\"{root}\\\",\\n div {\\n class: \\\"{props.cover_height.classes()} bg-muted bg-cover bg-center\\\",\\n style: cover_style(props.cover_image.as_deref()),\\n }\\n div { class: \\\"px-4 pb-4\\\",\\n div { class: \\\"-mt-10 flex items-end justify-between\\\",\\n div { class: \\\"size-20 overflow-hidden border-4 border-background bg-muted {props.avatar_shape.classes()}\\\",\\n if let Some(avatar) = &props.avatar {\\n img { src: \\\"{avatar}\\\", alt: \\\"\\\", class: \\\"size-full object-cover\\\", loading: \\\"lazy\\\" }\\n }\\n }\\n if let Some(action) = &props.action {\\n {action}\\n }\\n }\\n div { class: \\\"mt-3\\\",\\n div { class: \\\"flex items-center gap-2\\\",\\n h1 { class: \\\"text-xl font-bold text-foreground\\\", style: \\\"font-family: var(--font-serif);\\\",\\n \\\"{props.name}\\\"\\n }\\n if let Some(badge) = &props.badge {\\n {badge}\\n }\\n }\\n if let Some(subtitle) = &props.subtitle {\\n p { class: \\\"text-sm text-muted-foreground\\\", \\\"{subtitle}\\\" }\\n }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn no_cover_means_no_style() {\\n assert_eq!(cover_style(None), \\\"\\\");\\n }\\n\\n #[test]\\n fn a_cover_url_is_quoted() {\\n assert_eq!(\\n cover_style(Some(\\\"https://x.test/a b.jpg\\\")),\\n \\\"background-image: url(\\\\\\\"https://x.test/a b.jpg\\\\\\\");\\\"\\n );\\n }\\n\\n #[test]\\n fn a_quote_in_the_url_cannot_end_the_string() {\\n assert_eq!(\\n cover_style(Some(\\\"x\\\\\\\").y\\\")),\\n \\\"background-image: url(\\\\\\\"x\\\\\\\\\\\\\\\").y\\\\\\\");\\\"\\n );\\n }\\n}\\n\"}],\"contract\":\"contract\\n slot is \\\"mzizi-cover-header\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-cover-header\\\"\\n role is \\\"banner\\\"\\n cover_height.md class is \\\"h-32 sm:h-48\\\"\\n every avatar_shape class contains \\\"rounded-\\\"\\nend\"}",
|
|
1209
|
+
"mzizi-deep-link-handler": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-deep-link-handler\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Universal link and deep link routing handler. Maps external URLs, push notification taps, and share links to in-app routes. Handles: mukoko.com/{path}, mini-app deep links, push notification payloads, NFC/QR scan results.\",\"crate\":{\"name\":\"mzizi-shell\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n7-shell/mzizi-deep-link-handler.rs\",\"type\":\"registry:rust\",\"content\":\"//! NYUCHI DEEP LINK HANDLER — N7 shell, Dioxus.\\n//!\\n//! The Rust sibling of `mzizi-deep-link-handler.tsx`: match an incoming URL against a list\\n//! of routes and dispatch the first one that fits.\\n//!\\n//! # What is actually being ported\\n//!\\n//! The `.tsx` component is thin — `<div>{children}</div>` plus two `useEffect`s wiring\\n//! `window.addEventListener` for `popstate`/a custom event and the initial URL. Browser event\\n//! wiring is not something a registry crate can usefully abstract across Dioxus's web/desktop/\\n//! mobile targets without guessing which one the consumer is building for. What is genuinely\\n//! portable, and what this actually ports, is the MATCHING ALGORITHM: given a URL and a route\\n//! table, which handler fires and with what captured parameters. [`resolve`] is that function,\\n//! fully unit-testable with no DOM. The `#[component]` wrapper renders the same\\n//! `data-slot`/`data-portal` div and takes the resolved outcome as a prop, so a host wires\\n//! its own event source (`window.addEventListener`, a router's location, a deep-link intent\\n//! on mobile) and calls [`resolve`] itself.\\n//!\\n//! # A silent-failure mode in the TypeScript, not reproduced\\n//!\\n//! For a `RegExp` route (as opposed to a `\\\":param\\\"` string pattern), the `.tsx` requires\\n//! `match.groups` to be truthy before calling the handler:\\n//!\\n//! ```text\\n//! const match = url.match(route.pattern)\\n//! if (match?.groups) { route.handler(match.groups); return }\\n//! ```\\n//!\\n//! A plain `RegExp` with no named capture groups — `/^\\\\/settings$/`, say — matches the URL\\n//! but produces `groups: undefined`, so the condition is false and the route is treated as\\n//! not matching at all. A route with a literal pattern and no params can never fire, and\\n//! nothing says why: it falls through to `onUnmatched` exactly as if the URL were unrelated.\\n//!\\n//! [`RoutePattern::Regex`] does not require named groups: a bare match is enough, and\\n//! [`Captures`] is empty when there are none. A route with no params behaves like a route\\n//! with no params, not like a route that silently does not exist.\\n//!\\n//! **The `.tsx` sibling still has this.**\\n\\nuse std::collections::BTreeMap;\\n\\nuse dioxus::prelude::*;\\nuse regex::Regex;\\n\\n/// Named parameters captured out of a URL by a matching route.\\npub type Captures = BTreeMap<String, String>;\\n\\n/// How a route's pattern is expressed.\\n#[derive(Debug, Clone)]\\npub enum RoutePattern {\\n /// A path template with `:name` segments, e.g. `/users/:id`. Converted to an anchored\\n /// regex with one named capture group per segment, exactly as the `.tsx`'s\\n /// `pattern.replace(/:(\\\\w+)/g, \\\"(?<$1>[^/]+)\\\")` does.\\n Template(String),\\n /// A raw, pre-anchored regex. Unlike the `.tsx`, a match with no named groups is not\\n /// treated as a non-match — see the module docs.\\n Regex(Regex),\\n}\\n\\n/// One deep-link route: a pattern and an identifier the host resolves to a handler.\\n///\\n/// The `.tsx`'s `handler` is a closure held on the route; here `id` is a plain value and the\\n/// host looks up its own handler after [`resolve`] returns, keeping this module free of\\n/// `Box<dyn Fn>` and the lifetime questions that come with it.\\n#[derive(Debug, Clone)]\\npub struct Route {\\n /// The pattern to match.\\n pub pattern: RoutePattern,\\n /// Opaque identifier the host maps back to a handler.\\n pub id: String,\\n}\\n\\nimpl Route {\\n /// A route from a `:param` template.\\n #[must_use]\\n pub fn template(pattern: &str, id: &str) -> Self {\\n Self {\\n pattern: RoutePattern::Template(pattern.to_owned()),\\n id: id.to_owned(),\\n }\\n }\\n\\n /// A route from a raw regex.\\n #[must_use]\\n pub fn regex(pattern: Regex, id: &str) -> Self {\\n Self {\\n pattern: RoutePattern::Regex(pattern),\\n id: id.to_owned(),\\n }\\n }\\n\\n fn compiled(&self) -> Option<Regex> {\\n match &self.pattern {\\n RoutePattern::Regex(r) => Some(r.clone()),\\n RoutePattern::Template(t) => {\\n // `:name` → `(?P<name>[^/]+)`, anchored — the Rust equivalent of the `.tsx`'s\\n // `\\\"^\\\" + pattern.replace(/:(\\\\w+)/g, \\\"(?<$1>[^/]+)\\\") + \\\"$\\\"`.\\n let mut out = String::from(\\\"^\\\");\\n let mut chars = t.chars().peekable();\\n while let Some(c) = chars.next() {\\n if c == ':' {\\n let mut name = String::new();\\n while let Some(&n) = chars.peek() {\\n if n.is_alphanumeric() || n == '_' {\\n name.push(n);\\n chars.next();\\n } else {\\n break;\\n }\\n }\\n out.push_str(&format!(\\\"(?P<{name}>[^/]+)\\\"));\\n } else {\\n // Escape regex metacharacters from the literal segments, which a\\n // template author does not expect to need escaping in a path.\\n if \\\"\\\\\\\\.+*?()|[]{}^$\\\".contains(c) {\\n out.push('\\\\\\\\');\\n }\\n out.push(c);\\n }\\n }\\n out.push('$');\\n Regex::new(&out).ok()\\n }\\n }\\n }\\n}\\n\\n/// What matching a URL against a route table produced.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub enum ResolveOutcome {\\n /// A route matched. Carries the winning route's `id` and its captures.\\n Matched {\\n /// The matching route's [`Route::id`].\\n id: String,\\n /// Named captures, empty for a route with no params.\\n captures: Captures,\\n },\\n /// No route matched.\\n Unmatched,\\n}\\n\\n/// Match a URL against a route table, first match wins — same order-dependence as the `.tsx`'s\\n/// `for (const route of routes)` loop.\\n#[must_use]\\npub fn resolve(routes: &[Route], url: &str) -> ResolveOutcome {\\n for route in routes {\\n let Some(re) = route.compiled() else { continue };\\n let Some(caps) = re.captures(url) else {\\n continue;\\n };\\n let mut captures = Captures::new();\\n for name in re.capture_names().flatten() {\\n if let Some(m) = caps.name(name) {\\n captures.insert(name.to_owned(), m.as_str().to_owned());\\n }\\n }\\n return ResolveOutcome::Matched {\\n id: route.id.clone(),\\n captures,\\n };\\n }\\n ResolveOutcome::Unmatched\\n}\\n\\n/// Props for [`DeepLinkHandler`].\\n///\\n/// Deliberately thin: this component renders the wrapper div. The host performs its own\\n/// event wiring (`popstate`, a custom event, a mobile deep-link intent) and calls [`resolve`]\\n/// itself — see the module docs.\\n#[derive(Props, Clone, PartialEq)]\\npub struct DeepLinkHandlerProps {\\n /// Wrapped content.\\n pub children: Element,\\n}\\n\\n/// The wrapper a deep-link-aware app renders its content inside.\\n#[component]\\npub fn DeepLinkHandler(props: DeepLinkHandlerProps) -> Element {\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-deep-link-handler\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-deep-link-handler\\\",\\n {props.children}\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn a_template_route_captures_named_params() {\\n let routes = vec![Route::template(\\\"/users/:id\\\", \\\"user\\\")];\\n let ResolveOutcome::Matched { id, captures } = resolve(&routes, \\\"/users/42\\\") else {\\n panic!(\\\"expected a match\\\")\\n };\\n assert_eq!(id, \\\"user\\\");\\n assert_eq!(captures.get(\\\"id\\\"), Some(&\\\"42\\\".to_owned()));\\n }\\n\\n #[test]\\n fn a_template_route_with_two_params_captures_both() {\\n let routes = vec![Route::template(\\\"/orgs/:org/repos/:repo\\\", \\\"repo\\\")];\\n let ResolveOutcome::Matched { captures, .. } = resolve(&routes, \\\"/orgs/nyuchi/repos/mzizi\\\")\\n else {\\n panic!(\\\"expected a match\\\")\\n };\\n assert_eq!(captures.get(\\\"org\\\"), Some(&\\\"nyuchi\\\".to_owned()));\\n assert_eq!(captures.get(\\\"repo\\\"), Some(&\\\"mzizi\\\".to_owned()));\\n }\\n\\n #[test]\\n fn first_matching_route_wins() {\\n let routes = vec![\\n Route::template(\\\"/users/:id\\\", \\\"user\\\"),\\n Route::template(\\\"/:anything\\\", \\\"catchall\\\"),\\n ];\\n let ResolveOutcome::Matched { id, .. } = resolve(&routes, \\\"/users/1\\\") else {\\n panic!(\\\"expected a match\\\")\\n };\\n assert_eq!(id, \\\"user\\\");\\n }\\n\\n #[test]\\n fn no_route_matches_is_unmatched() {\\n let routes = vec![Route::template(\\\"/users/:id\\\", \\\"user\\\")];\\n assert_eq!(resolve(&routes, \\\"/settings\\\"), ResolveOutcome::Unmatched);\\n }\\n\\n #[test]\\n fn a_regex_route_with_no_named_groups_still_matches() {\\n // The defect this fixes: the .tsx requires match.groups to be truthy, so a route\\n // with no params can never fire and silently falls through to unmatched.\\n let routes = vec![Route::regex(Regex::new(\\\"^/settings$\\\").unwrap(), \\\"settings\\\")];\\n let ResolveOutcome::Matched { id, captures } = resolve(&routes, \\\"/settings\\\") else {\\n panic!(\\\"a route with no named groups must still be able to match\\\")\\n };\\n assert_eq!(id, \\\"settings\\\");\\n assert!(captures.is_empty());\\n }\\n\\n #[test]\\n fn a_regex_route_with_named_groups_captures_them() {\\n let routes = vec![Route::regex(\\n Regex::new(r\\\"^/legacy/(?P<slug>[^/]+)$\\\").unwrap(),\\n \\\"legacy\\\",\\n )];\\n let ResolveOutcome::Matched { captures, .. } = resolve(&routes, \\\"/legacy/old-page\\\") else {\\n panic!(\\\"expected a match\\\")\\n };\\n assert_eq!(captures.get(\\\"slug\\\"), Some(&\\\"old-page\\\".to_owned()));\\n }\\n\\n #[test]\\n fn literal_characters_in_a_template_are_escaped() {\\n // A path segment containing a regex metacharacter must be matched literally.\\n let routes = vec![Route::template(\\\"/v1.0/:id\\\", \\\"versioned\\\")];\\n assert_eq!(resolve(&routes, \\\"/v1x0/5\\\"), ResolveOutcome::Unmatched);\\n let ResolveOutcome::Matched { captures, .. } = resolve(&routes, \\\"/v1.0/5\\\") else {\\n panic!(\\\"the literal dot must match a literal dot\\\")\\n };\\n assert_eq!(captures.get(\\\"id\\\"), Some(&\\\"5\\\".to_owned()));\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1210
|
+
"mzizi-docs-api": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-docs-api\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"Documentation read API for N10. Runs as a Supabase Deno edge function (`Deno.serve`, `jsr:@supabase/supabase-js@2`) — NOT a Cloudflare Worker and not Rust/WASM, which is what this description claimed while the file said otherwise. Serves documentation pages, AI instructions, architecture nodes and changelog entries over PostgREST: /docs, /ai-instructions, /architecture, /changelog, /counts. Public read, no auth. Deno-runtime source, so it is excluded from tsconfig and is not installable into a React app via the shadcn CLI.\",\"crate\":{\"name\":\"mzizi-docs\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n10-documentation/mzizi-docs-api.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N10 documentation — the read layer over published documentation.\\n//!\\n//! The Rust implementation of `mzizi-docs-api`. Routing and response shaping;\\n//! the host owns the data source and the credential.\\n//!\\n//! # Two security defects in the TypeScript, both structurally impossible here\\n//!\\n//! **1. Filter-structure injection.** The `.ts` builds a PostgREST filter by\\n//! string interpolation from a URL path segment:\\n//!\\n//! ```text\\n//! .or(`name.eq.${identifier},target.eq.${identifier}`)\\n//! ```\\n//!\\n//! `identifier` is `rest[0]` — whatever the caller put in the path, unescaped.\\n//! PostgREST reads `,` as a disjunction separator and `.` as the operator\\n//! separator, so `/ai-instructions/x,status.eq.draft` does not look up `x`; it\\n//! adds a condition. The `.eq(\\\"status\\\", \\\"active\\\")` above is a separate `and`\\n//! term, so what the injected clause widens is which rows the `or` matches, and\\n//! a caller can enumerate rows the route was written to keep hidden. This is the\\n//! same class `lib/db`'s `searchComponents` carries a comment about having\\n//! removed — \\\"the old implementation had to strip PostgREST-significant\\n//! characters to avoid filter-structure injection\\\" — so the app fixed it and the\\n//! published component did not.\\n//!\\n//! Here the identifier never becomes filter syntax. [`Route::AiInstruction`]\\n//! carries it as a value, and [`DocsStore::ai_instruction_by_name_or_target`]\\n//! receives it as a parameter for the host to bind. There is no string to\\n//! restructure.\\n//!\\n//! **2. A service-role credential in a publicly installable component.** The\\n//! `.ts` reads `SUPABASE_SERVICE_ROLE_KEY`, so anyone who runs `shadcn add` on\\n//! this item installs a function that bypasses RLS — over a read layer for\\n//! *published* documentation, which needs no such thing. Its own header says as\\n//! much and calls it out of scope.\\n//!\\n//! It is in scope for this port, and the fix is structural rather than a swapped\\n//! constant: this module holds no client and reads no environment. The host\\n//! implements [`DocsStore`] with whatever identity it judges correct, and an\\n//! anon client under RLS satisfies the trait completely.\\n//!\\n//! **The `.ts` sibling still has both.** Neither is fixed by this file.\\n//!\\n//! # What this owns\\n//!\\n//! Parsing a path into a [`Route`], asking a [`DocsStore`], and shaping the\\n//! answer into an [`ApiResponse`] with the right status and headers. It does not\\n//! serve — `Deno.serve` has no Rust equivalent worth pretending to, and the same\\n//! division holds as in N8, which builds an OTLP request and lets the host send\\n//! it.\\n\\nuse std::collections::BTreeMap;\\n\\n/// CORS headers, matching the `.ts`.\\n#[must_use]\\npub fn cors_headers() -> Vec<(&'static str, &'static str)> {\\n vec![\\n (\\\"Access-Control-Allow-Origin\\\", \\\"*\\\"),\\n (\\n \\\"Access-Control-Allow-Headers\\\",\\n \\\"authorization, x-client-info, apikey, content-type\\\",\\n ),\\n (\\\"Access-Control-Allow-Methods\\\", \\\"GET, OPTIONS\\\"),\\n ]\\n}\\n\\n/// Cache headers, matching the `.ts`.\\n#[must_use]\\npub fn cache_headers() -> Vec<(&'static str, &'static str)> {\\n vec![(\\n \\\"Cache-Control\\\",\\n \\\"public, max-age=60, stale-while-revalidate=300\\\",\\n )]\\n}\\n\\n/// A route this API answers.\\n///\\n/// An enum rather than nested string comparisons, so an unhandled combination is\\n/// a compile error in the host's `match` instead of a fallthrough to 404.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub enum Route {\\n /// `OPTIONS *` — CORS preflight.\\n Preflight,\\n /// `GET /counts`\\n Counts,\\n /// `GET /docs`\\n DocsList,\\n /// `GET /docs/:slug`\\n Doc(String),\\n /// `GET /docs/category/:category`\\n DocsByCategory(String),\\n /// `GET /ai-instructions`\\n AiInstructionsList,\\n /// `GET /ai-instructions/:identifier` — matched against name OR target.\\n AiInstruction(String),\\n /// `GET /architecture`\\n Architecture,\\n /// `GET /architecture/nodes`\\n ArchitectureNodes,\\n /// `GET /architecture/axes`\\n ///\\n /// Retained because the `.ts` exposes it, but see [`Route::ARCHITECTURE_AXES_IS_RETIRED`].\\n ArchitectureAxes,\\n /// `GET /changelog?limit=&offset=`\\n Changelog {\\n /// Clamped to 100, as in the `.ts`.\\n limit: u32,\\n /// Clamped to at least 0 — unsigned here, so that is free.\\n offset: u32,\\n },\\n /// `GET /changelog/:version`\\n ChangelogEntry(String),\\n /// Anything else.\\n NotFound,\\n /// A method other than GET or OPTIONS.\\n MethodNotAllowed,\\n}\\n\\nimpl Route {\\n /// `/architecture/axes` describes the retired layer-and-axis model.\\n ///\\n /// `api.mzizi.dev/v1/architecture/axes` answers `410 Gone` today. The route\\n /// is kept here so a host serving the `.ts`'s published surface can decide\\n /// deliberately — answer it, or return 410 to match production — rather than\\n /// discovering the discrepancy from a consumer.\\n pub const ARCHITECTURE_AXES_IS_RETIRED: bool = true;\\n\\n /// Default page size for `/changelog`, matching the `.ts`.\\n pub const CHANGELOG_DEFAULT_LIMIT: u32 = 20;\\n /// Hard cap on `/changelog` page size, matching the `.ts` `Math.min(limit, 100)`.\\n pub const CHANGELOG_MAX_LIMIT: u32 = 100;\\n}\\n\\n/// Parse a query string into its pairs. Last value wins, as `URLSearchParams.get` does.\\nfn query_pairs(query: &str) -> BTreeMap<&str, &str> {\\n let mut out = BTreeMap::new();\\n for pair in query.split('&').filter(|p| !p.is_empty()) {\\n let (k, v) = pair.split_once('=').unwrap_or((pair, \\\"\\\"));\\n out.insert(k, v);\\n }\\n out\\n}\\n\\n/// Read a `u32` query param, falling back when absent or unparseable.\\n///\\n/// The `.ts` uses `parseInt`, which yields `NaN` for `?limit=abc`; `Math.min(NaN,\\n/// 100)` is `NaN`, and `NaN` then goes to the database as the page size. Here an\\n/// unparseable value takes the default, which is what the caller meant and what\\n/// the database can act on.\\nfn u32_param(params: &BTreeMap<&str, &str>, key: &str, default: u32) -> u32 {\\n params\\n .get(key)\\n .and_then(|v| v.parse().ok())\\n .unwrap_or(default)\\n}\\n\\n/// Route a request.\\n///\\n/// `method` is compared case-sensitively against `GET`/`OPTIONS`, as the `.ts`\\n/// compares `req.method`.\\n#[must_use]\\npub fn route(method: &str, path: &str, query: &str) -> Route {\\n if method == \\\"OPTIONS\\\" {\\n return Route::Preflight;\\n }\\n if method != \\\"GET\\\" {\\n return Route::MethodNotAllowed;\\n }\\n\\n let segments: Vec<&str> = path\\n .trim_start_matches('/')\\n .split('/')\\n .filter(|s| !s.is_empty())\\n .collect();\\n\\n match segments.as_slice() {\\n [\\\"counts\\\"] => Route::Counts,\\n\\n [\\\"docs\\\"] => Route::DocsList,\\n [\\\"docs\\\", \\\"category\\\"] => Route::NotFound,\\n [\\\"docs\\\", \\\"category\\\", category] => Route::DocsByCategory((*category).to_owned()),\\n [\\\"docs\\\", slug] => Route::Doc((*slug).to_owned()),\\n\\n [\\\"ai-instructions\\\"] => Route::AiInstructionsList,\\n [\\\"ai-instructions\\\", id] => Route::AiInstruction((*id).to_owned()),\\n\\n [\\\"architecture\\\"] => Route::Architecture,\\n [\\\"architecture\\\", \\\"nodes\\\"] => Route::ArchitectureNodes,\\n [\\\"architecture\\\", \\\"axes\\\"] => Route::ArchitectureAxes,\\n\\n [\\\"changelog\\\"] => {\\n let params = query_pairs(query);\\n Route::Changelog {\\n limit: u32_param(¶ms, \\\"limit\\\", Route::CHANGELOG_DEFAULT_LIMIT)\\n .min(Route::CHANGELOG_MAX_LIMIT),\\n offset: u32_param(¶ms, \\\"offset\\\", 0),\\n }\\n }\\n [\\\"changelog\\\", version] => Route::ChangelogEntry((*version).to_owned()),\\n\\n _ => Route::NotFound,\\n }\\n}\\n\\n/// What a store hands back: a JSON document, already serialised.\\n///\\n/// An opaque `String` rather than a parsed value, because this module reshapes\\n/// nothing — the `.ts` passes rows through untouched and so does this. Keeping it\\n/// opaque also means no JSON dependency, matching N8, N9 and N11.\\npub type JsonDoc = String;\\n\\n/// Why a store could not answer.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub enum StoreError {\\n /// The row does not exist. Becomes 404.\\n NotFound,\\n /// Anything else. Becomes 500, and the message is NOT returned to the caller.\\n Backend(String),\\n}\\n\\n/// The data the routes need.\\n///\\n/// A trait, so this component holds no client and no credential. Every method\\n/// takes its filter values as PARAMETERS — never as a fragment of query syntax —\\n/// which is what makes the `.ts`'s `or(...)` injection unrepresentable here.\\n///\\n/// An implementation backed by an anon Supabase client under RLS satisfies this\\n/// completely; nothing in the contract needs service-role.\\npub trait DocsStore {\\n /// `get_system_counts()`.\\n fn system_counts(&self) -> Result<JsonDoc, StoreError>;\\n /// Published pages, metadata only, ordered by category then sort order.\\n fn docs_list(&self) -> Result<JsonDoc, StoreError>;\\n /// One published page by slug, with its content.\\n fn doc_by_slug(&self, slug: &str) -> Result<JsonDoc, StoreError>;\\n /// Published pages in one category, ordered by sort order.\\n fn docs_by_category(&self, category: &str) -> Result<JsonDoc, StoreError>;\\n /// Active instruction sets, metadata only.\\n fn ai_instructions_list(&self) -> Result<JsonDoc, StoreError>;\\n /// The latest active instruction set whose NAME or TARGET equals `identifier`.\\n ///\\n /// Two bound parameters and a disjunction the implementation writes itself.\\n /// The identifier is a value here and can never become filter structure.\\n fn ai_instruction_by_name_or_target(&self, identifier: &str) -> Result<JsonDoc, StoreError>;\\n /// `get_architecture()`.\\n fn architecture(&self) -> Result<JsonDoc, StoreError>;\\n /// `get_node_counts()`.\\n fn architecture_nodes(&self) -> Result<JsonDoc, StoreError>;\\n /// `get_axes_summary()`. See [`Route::ARCHITECTURE_AXES_IS_RETIRED`].\\n fn architecture_axes(&self) -> Result<JsonDoc, StoreError>;\\n /// `list_changelog(limit, offset)`.\\n fn changelog(&self, limit: u32, offset: u32) -> Result<JsonDoc, StoreError>;\\n /// `get_changelog_entry(version)`.\\n fn changelog_entry(&self, version: &str) -> Result<JsonDoc, StoreError>;\\n}\\n\\n/// A response for the host to send.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct ApiResponse {\\n /// HTTP status.\\n pub status: u16,\\n /// Headers, including CORS.\\n pub headers: Vec<(String, String)>,\\n /// Body. Empty for a preflight.\\n pub body: String,\\n}\\n\\nfn respond(status: u16, body: String, cache: bool) -> ApiResponse {\\n let mut headers: Vec<(String, String)> =\\n vec![(\\\"Content-Type\\\".to_owned(), \\\"application/json\\\".to_owned())];\\n headers.extend(\\n cors_headers()\\n .into_iter()\\n .map(|(k, v)| (k.to_owned(), v.to_owned())),\\n );\\n if cache {\\n headers.extend(\\n cache_headers()\\n .into_iter()\\n .map(|(k, v)| (k.to_owned(), v.to_owned())),\\n );\\n }\\n ApiResponse {\\n status,\\n headers,\\n body,\\n }\\n}\\n\\n/// An error body. The message is a fixed string chosen here, never the backend's.\\n///\\n/// The `.ts` returns `err.message` from PostgREST with a 500 — which leaks table\\n/// names, column names and constraint text to any caller who can provoke an\\n/// error. [`StoreError::Backend`] carries the detail for the host to log; the\\n/// caller gets the status and nothing else.\\nfn error_response(status: u16, message: &str) -> ApiResponse {\\n let mut body = String::from(\\\"{\\\\\\\"error\\\\\\\":\\\\\\\"\\\");\\n for c in message.chars() {\\n match c {\\n '\\\"' => body.push_str(\\\"\\\\\\\\\\\\\\\"\\\"),\\n '\\\\\\\\' => body.push_str(\\\"\\\\\\\\\\\\\\\\\\\"),\\n '\\\\n' => body.push_str(\\\"\\\\\\\\n\\\"),\\n c if (c as u32) < 0x20 => body.push(' '),\\n c => body.push(c),\\n }\\n }\\n body.push_str(\\\"\\\\\\\"}\\\");\\n respond(status, body, false)\\n}\\n\\n/// Answer a route from a store.\\n///\\n/// Returns the response, plus any backend detail the host should log rather than\\n/// send. Splitting them is what stops the `.ts`'s habit of returning\\n/// `err.message` to the caller.\\npub fn handle<S: DocsStore + ?Sized>(route: &Route, store: &S) -> (ApiResponse, Option<String>) {\\n let result = match route {\\n Route::Preflight => {\\n let headers = cors_headers()\\n .into_iter()\\n .map(|(k, v)| (k.to_owned(), v.to_owned()))\\n .collect();\\n return (\\n ApiResponse {\\n status: 204,\\n headers,\\n body: String::new(),\\n },\\n None,\\n );\\n }\\n Route::MethodNotAllowed => {\\n return (error_response(405, \\\"Method not allowed\\\"), None);\\n }\\n Route::NotFound => return (error_response(404, \\\"Not found\\\"), None),\\n\\n Route::Counts => store.system_counts(),\\n Route::DocsList => store.docs_list(),\\n Route::Doc(slug) => store.doc_by_slug(slug),\\n Route::DocsByCategory(c) => store.docs_by_category(c),\\n Route::AiInstructionsList => store.ai_instructions_list(),\\n Route::AiInstruction(id) => store.ai_instruction_by_name_or_target(id),\\n Route::Architecture => store.architecture(),\\n Route::ArchitectureNodes => store.architecture_nodes(),\\n Route::ArchitectureAxes => store.architecture_axes(),\\n Route::Changelog { limit, offset } => store.changelog(*limit, *offset),\\n Route::ChangelogEntry(v) => store.changelog_entry(v),\\n };\\n\\n match result {\\n Ok(body) => (respond(200, body, true), None),\\n Err(StoreError::NotFound) => (error_response(404, \\\"Not found\\\"), None),\\n Err(StoreError::Backend(detail)) => {\\n (error_response(500, \\\"Internal server error\\\"), Some(detail))\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n struct Spy {\\n last_identifier: std::cell::RefCell<Option<String>>,\\n }\\n\\n impl Spy {\\n fn new() -> Self {\\n Self {\\n last_identifier: std::cell::RefCell::new(None),\\n }\\n }\\n }\\n\\n macro_rules! ok {\\n ($v:expr) => {\\n Ok($v.to_owned())\\n };\\n }\\n\\n impl DocsStore for Spy {\\n fn system_counts(&self) -> Result<JsonDoc, StoreError> {\\n ok!(\\\"{\\\\\\\"components\\\\\\\":575}\\\")\\n }\\n fn docs_list(&self) -> Result<JsonDoc, StoreError> {\\n ok!(\\\"[]\\\")\\n }\\n fn doc_by_slug(&self, slug: &str) -> Result<JsonDoc, StoreError> {\\n if slug == \\\"missing\\\" {\\n Err(StoreError::NotFound)\\n } else {\\n ok!(format!(\\\"{{\\\\\\\"slug\\\\\\\":\\\\\\\"{slug}\\\\\\\"}}\\\"))\\n }\\n }\\n fn docs_by_category(&self, _: &str) -> Result<JsonDoc, StoreError> {\\n ok!(\\\"[]\\\")\\n }\\n fn ai_instructions_list(&self) -> Result<JsonDoc, StoreError> {\\n ok!(\\\"[]\\\")\\n }\\n fn ai_instruction_by_name_or_target(&self, id: &str) -> Result<JsonDoc, StoreError> {\\n *self.last_identifier.borrow_mut() = Some(id.to_owned());\\n ok!(\\\"{}\\\")\\n }\\n fn architecture(&self) -> Result<JsonDoc, StoreError> {\\n ok!(\\\"{}\\\")\\n }\\n fn architecture_nodes(&self) -> Result<JsonDoc, StoreError> {\\n ok!(\\\"[]\\\")\\n }\\n fn architecture_axes(&self) -> Result<JsonDoc, StoreError> {\\n ok!(\\\"[]\\\")\\n }\\n fn changelog(&self, limit: u32, offset: u32) -> Result<JsonDoc, StoreError> {\\n ok!(format!(\\\"{{\\\\\\\"limit\\\\\\\":{limit},\\\\\\\"offset\\\\\\\":{offset}}}\\\"))\\n }\\n fn changelog_entry(&self, _: &str) -> Result<JsonDoc, StoreError> {\\n Err(StoreError::Backend(\\n \\\"relation \\\\\\\"changelog\\\\\\\" does not exist\\\".to_owned(),\\n ))\\n }\\n }\\n\\n #[test]\\n fn routes_match_the_documented_surface() {\\n assert_eq!(route(\\\"GET\\\", \\\"/counts\\\", \\\"\\\"), Route::Counts);\\n assert_eq!(route(\\\"GET\\\", \\\"/docs\\\", \\\"\\\"), Route::DocsList);\\n assert_eq!(route(\\\"GET\\\", \\\"/docs/intro\\\", \\\"\\\"), Route::Doc(\\\"intro\\\".into()));\\n assert_eq!(\\n route(\\\"GET\\\", \\\"/docs/category/guides\\\", \\\"\\\"),\\n Route::DocsByCategory(\\\"guides\\\".into())\\n );\\n assert_eq!(\\n route(\\\"GET\\\", \\\"/ai-instructions\\\", \\\"\\\"),\\n Route::AiInstructionsList\\n );\\n assert_eq!(\\n route(\\\"GET\\\", \\\"/architecture/nodes\\\", \\\"\\\"),\\n Route::ArchitectureNodes\\n );\\n assert_eq!(route(\\\"GET\\\", \\\"/nope\\\", \\\"\\\"), Route::NotFound);\\n }\\n\\n #[test]\\n fn a_bare_category_path_is_not_a_slug_called_category() {\\n // The .ts checks `sub !== \\\"category\\\"` to avoid exactly this.\\n assert_eq!(route(\\\"GET\\\", \\\"/docs/category\\\", \\\"\\\"), Route::NotFound);\\n }\\n\\n #[test]\\n fn method_handling_matches() {\\n assert_eq!(route(\\\"OPTIONS\\\", \\\"/docs\\\", \\\"\\\"), Route::Preflight);\\n assert_eq!(route(\\\"POST\\\", \\\"/docs\\\", \\\"\\\"), Route::MethodNotAllowed);\\n let (r, _) = handle(&Route::MethodNotAllowed, &Spy::new());\\n assert_eq!(r.status, 405);\\n }\\n\\n #[test]\\n fn changelog_paging_clamps_like_the_typescript() {\\n assert_eq!(\\n route(\\\"GET\\\", \\\"/changelog\\\", \\\"limit=500\\\"),\\n Route::Changelog {\\n limit: 100,\\n offset: 0\\n }\\n );\\n assert_eq!(\\n route(\\\"GET\\\", \\\"/changelog\\\", \\\"limit=5&offset=10\\\"),\\n Route::Changelog {\\n limit: 5,\\n offset: 10\\n }\\n );\\n }\\n\\n #[test]\\n fn an_unparseable_limit_takes_the_default_not_nan() {\\n // parseInt(\\\"abc\\\") is NaN in the .ts, and NaN reaches the database.\\n assert_eq!(\\n route(\\\"GET\\\", \\\"/changelog\\\", \\\"limit=abc\\\"),\\n Route::Changelog {\\n limit: 20,\\n offset: 0\\n }\\n );\\n }\\n\\n #[test]\\n fn a_negative_offset_cannot_be_expressed() {\\n // The .ts needs Math.max(offset, 0); u32 makes it unrepresentable.\\n assert_eq!(\\n route(\\\"GET\\\", \\\"/changelog\\\", \\\"offset=-5\\\"),\\n Route::Changelog {\\n limit: 20,\\n offset: 0\\n }\\n );\\n }\\n\\n #[test]\\n fn an_injection_attempt_stays_one_opaque_value() {\\n // The .ts interpolates this into `or(name.eq.${id},target.eq.${id})`,\\n // where the comma and dots are filter STRUCTURE.\\n let hostile = \\\"x,status.eq.draft\\\";\\n let r = route(\\\"GET\\\", &format!(\\\"/ai-instructions/{hostile}\\\"), \\\"\\\");\\n assert_eq!(r, Route::AiInstruction(hostile.to_owned()));\\n\\n let spy = Spy::new();\\n let (resp, _) = handle(&r, &spy);\\n assert_eq!(resp.status, 200);\\n // It reached the store as ONE parameter, intact and unsplit — there is no\\n // filter string for it to be part of.\\n assert_eq!(spy.last_identifier.borrow().as_deref(), Some(hostile));\\n }\\n\\n #[test]\\n fn backend_detail_is_logged_not_returned() {\\n let (resp, log) = handle(&Route::ChangelogEntry(\\\"4.0.0\\\".into()), &Spy::new());\\n assert_eq!(resp.status, 500);\\n assert!(resp.body.contains(\\\"Internal server error\\\"));\\n // The .ts returns err.message, which names the relation.\\n assert!(!resp.body.contains(\\\"relation\\\"));\\n assert!(log.unwrap().contains(\\\"relation\\\"));\\n }\\n\\n #[test]\\n fn not_found_from_the_store_becomes_404() {\\n let (resp, log) = handle(&Route::Doc(\\\"missing\\\".into()), &Spy::new());\\n assert_eq!(resp.status, 404);\\n assert!(log.is_none());\\n }\\n\\n #[test]\\n fn successful_responses_carry_cors_and_cache() {\\n let (resp, _) = handle(&Route::Counts, &Spy::new());\\n assert_eq!(resp.status, 200);\\n let names: Vec<&str> = resp.headers.iter().map(|(k, _)| k.as_str()).collect();\\n assert!(names.contains(&\\\"Access-Control-Allow-Origin\\\"));\\n assert!(names.contains(&\\\"Cache-Control\\\"));\\n }\\n\\n #[test]\\n fn errors_carry_cors_but_not_cache() {\\n let (resp, _) = handle(&Route::NotFound, &Spy::new());\\n let names: Vec<&str> = resp.headers.iter().map(|(k, _)| k.as_str()).collect();\\n assert!(names.contains(&\\\"Access-Control-Allow-Origin\\\"));\\n assert!(\\n !names.contains(&\\\"Cache-Control\\\"),\\n \\\"a 404 must not be cached for 60s\\\"\\n );\\n }\\n\\n #[test]\\n fn preflight_has_no_body() {\\n let (resp, _) = handle(&Route::Preflight, &Spy::new());\\n assert_eq!(resp.status, 204);\\n assert!(resp.body.is_empty());\\n }\\n\\n #[test]\\n fn error_bodies_escape_their_message() {\\n let r = error_response(400, \\\"bad \\\\\\\"quoted\\\\\\\" thing\\\");\\n assert!(r.body.contains(\\\"\\\\\\\\\\\\\\\"quoted\\\\\\\\\\\\\\\"\\\"));\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1211
|
+
"mzizi-docs-engine": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-docs-engine\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Documentation rendering engine for N10. Full-screen docs portal with sidebar navigation, search (title + description + keywords), MDX-delegate content rendering, related ecosystem node badges, and keyboard navigation. Reads from documentation_pages via mzizi-docs-api.\",\"crate\":{\"name\":\"mzizi-docs\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n10-documentation/mzizi-docs-engine.rs\",\"type\":\"registry:rust\",\"content\":\"//! NYUCHI DOCS ENGINE — N10 documentation, Dioxus.\\n//!\\n//! The Rust sibling of `mzizi-docs-engine.tsx`, sharing its contract: the same `data-slot`,\\n//! the same sidebar/main split, and the same class strings, so a Dioxus app renders markup a\\n//! React-authored stylesheet already styles.\\n//!\\n//! # Two accessibility defects in the TypeScript, fixed here\\n//!\\n//! **1. `role=\\\"listitem\\\"` on a `<button>` destroys the button.** The `.tsx` puts\\n//! `role=\\\"list\\\"` on a `<nav>` and `role=\\\"listitem\\\"` on each `<button>` inside it. An explicit\\n//! role REPLACES the implicit one, so assistive technology is told these are list items, not\\n//! buttons — a screen-reader user loses the \\\"button\\\" announcement and the interaction hint on\\n//! every navigation control in the sidebar. The `listitem` is invalid regardless: its required\\n//! owner is a `list`, and `<nav role=\\\"list\\\">` is a role clash of its own.\\n//!\\n//! Here the nav holds a `<ul>` of `<li>`s, each containing a plain `<button>`. The list\\n//! semantics are real, and the buttons stay buttons.\\n//!\\n//! **2. `min-h-[44px]` is below the floor this system publishes.** N10's own\\n//! `mzizi-ai-context` states the rule as \\\"Touch targets: 48px minimum\\\", and the sidebar\\n//! entries — the primary navigation of a documentation portal, on a mobile viewport — are\\n//! 44. That is Apple's number, not this system's. Raised to `min-h-[48px]`; nothing else about\\n//! the control changes.\\n//!\\n//! **The `.tsx` sibling still has both.**\\n//!\\n//! # A hydration hazard not reproduced\\n//!\\n//! The `.tsx` renders `new Date(updatedAt).toLocaleDateString()`. That formats against\\n//! whatever locale and timezone the runtime has, so a server render and a client render can\\n//! disagree and React reports a hydration mismatch — and the visible date depends on where\\n//! the process runs rather than who is reading. [`DocPage::updated_at`] is a pre-formatted\\n//! string, like `ChangelogEntry::date` next door: the host knows the viewer's locale and this\\n//! component does not.\\n//!\\n//! # The node table, a third time in this node\\n//!\\n//! `NODE_LABELS` is hardcoded N1–N10 again. Its fallback is at least graceful — an unknown\\n//! node renders `N{n}` — so the visible damage is smaller than in the changelog renderer, but\\n//! it is the same literal going stale. [`node_label`] takes the table as data.\\n\\nuse dioxus::prelude::*;\\n\\n/// One documentation page.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct DocPage {\\n /// URL slug, unique.\\n pub slug: String,\\n /// Page title.\\n pub title: String,\\n /// Grouping for the sidebar. Hyphens render as spaces.\\n pub category: String,\\n /// Optional finer grouping. Carried for parity with the `.tsx` and the\\n /// `documentation_pages` column; the sidebar groups by `category` alone.\\n pub subcategory: Option<String>,\\n /// Markdown/MDX body. Rendered by the host — see [`DocsEngineProps::render_content`].\\n pub content: String,\\n /// Short summary.\\n pub description: Option<String>,\\n /// Search keywords.\\n pub keywords: Vec<String>,\\n /// Node numbers this page relates to. Mirrors `documentation_pages.related_nodes`.\\n pub related_nodes: Vec<u16>,\\n /// Related component names.\\n pub related_components: Vec<String>,\\n /// Version this page documents.\\n pub version: Option<String>,\\n /// Last-updated, ALREADY FORMATTED by the host. See the module docs.\\n pub updated_at: Option<String>,\\n}\\n\\nimpl DocPage {\\n /// A page with only the required fields.\\n #[must_use]\\n pub fn new(slug: &str, title: &str, category: &str, content: &str) -> Self {\\n Self {\\n slug: slug.to_owned(),\\n title: title.to_owned(),\\n category: category.to_owned(),\\n subcategory: None,\\n content: content.to_owned(),\\n description: None,\\n keywords: Vec::new(),\\n related_nodes: Vec::new(),\\n related_components: Vec::new(),\\n version: None,\\n updated_at: None,\\n }\\n }\\n\\n /// Whether this page matches a search query.\\n ///\\n /// Title, description and keywords, case-insensitively — the same three fields the `.tsx`\\n /// searches, in the same order.\\n #[must_use]\\n pub fn matches(&self, query: &str) -> bool {\\n let q = query.to_lowercase();\\n self.title.to_lowercase().contains(&q)\\n || self\\n .description\\n .as_deref()\\n .is_some_and(|d| d.to_lowercase().contains(&q))\\n || self.keywords.iter().any(|k| k.to_lowercase().contains(&q))\\n }\\n}\\n\\n/// A node number to its sidebar label.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct NodeLabel {\\n /// Node number. Uncapped.\\n pub number: u16,\\n /// Display text, e.g. `N2 Primitives`.\\n pub label: String,\\n}\\n\\n/// The node labels as they stand. A DEFAULT, not a definition — see the module docs.\\n#[must_use]\\npub fn default_node_labels() -> Vec<NodeLabel> {\\n [\\n (1, \\\"N1 Tokens\\\"),\\n (2, \\\"N2 Primitives\\\"),\\n (3, \\\"N3 Brand\\\"),\\n (4, \\\"N4 Safety\\\"),\\n (5, \\\"N5 Resilience\\\"),\\n (6, \\\"N6 Pages\\\"),\\n (7, \\\"N7 Shell\\\"),\\n (8, \\\"N8 Assurance\\\"),\\n (9, \\\"N9 Fundi\\\"),\\n (10, \\\"N10 Docs\\\"),\\n (11, \\\"N11 Discovery\\\"),\\n (12, \\\"N12 Skills\\\"),\\n ]\\n .into_iter()\\n .map(|(number, label)| NodeLabel {\\n number,\\n label: label.to_owned(),\\n })\\n .collect()\\n}\\n\\n/// A node's label, falling back to `N{n}` exactly as the `.tsx` does.\\n#[must_use]\\npub fn node_label(labels: &[NodeLabel], n: u16) -> String {\\n labels\\n .iter()\\n .find(|l| l.number == n)\\n .map_or_else(|| format!(\\\"N{n}\\\"), |l| l.label.clone())\\n}\\n\\n/// Filter pages by a search query, preserving order.\\n///\\n/// An empty or whitespace-only query returns everything, matching the `.tsx` `search.trim()`.\\n#[must_use]\\npub fn filter_pages(pages: &[DocPage], search: &str) -> Vec<DocPage> {\\n if search.trim().is_empty() {\\n return pages.to_vec();\\n }\\n pages\\n .iter()\\n .filter(|p| p.matches(search))\\n .cloned()\\n .collect()\\n}\\n\\n/// Distinct categories, in first-appearance order.\\n///\\n/// The `.tsx` uses `[...new Set(...)]`, which preserves insertion order; a `HashSet` here\\n/// would reorder the sidebar between runs.\\n#[must_use]\\npub fn categories(pages: &[DocPage]) -> Vec<String> {\\n let mut seen: Vec<String> = Vec::new();\\n for p in pages {\\n if !seen.iter().any(|c| c == &p.category) {\\n seen.push(p.category.clone());\\n }\\n }\\n seen\\n}\\n\\n/// A category slug as the sidebar shows it: hyphens become spaces.\\n#[must_use]\\npub fn category_label(category: &str) -> String {\\n category.replace('-', \\\" \\\")\\n}\\n\\nconst ROOT: &str = \\\"flex h-screen overflow-hidden bg-background\\\";\\nconst ASIDE: &str = \\\"w-64 shrink-0 overflow-y-auto border-r border-border p-4\\\";\\nconst SEARCH_INPUT: &str = \\\"min-h-[48px] w-full rounded-[var(--radius-md,12px)] border border-border bg-input px-3 py-2 text-sm focus-visible:outline-2 focus-visible:outline-[var(--ring)]\\\";\\nconst CATEGORY_HEADING: &str =\\n \\\"mb-1 px-2 text-xs font-medium tracking-wider text-muted-foreground uppercase\\\";\\n/// The `.tsx` says `min-h-[44px]`. See the module docs.\\nconst NAV_ITEM: &str = \\\"block min-h-[48px] w-full rounded-[var(--radius-sm,7px)] px-2 py-1.5 text-left text-sm transition-colors focus-visible:outline-2 focus-visible:outline-[var(--ring)]\\\";\\nconst NAV_ITEM_CURRENT: &str = \\\"bg-muted font-medium text-foreground\\\";\\nconst NAV_ITEM_IDLE: &str = \\\"text-muted-foreground hover:bg-muted hover:text-foreground\\\";\\nconst MAIN: &str = \\\"flex-1 overflow-y-auto p-8\\\";\\nconst PAGE_TITLE: &str = \\\"font-serif text-3xl font-bold\\\";\\nconst PAGE_DESCRIPTION: &str = \\\"mt-2 text-lg text-muted-foreground\\\";\\nconst RELATED_NODE: &str =\\n \\\"rounded-full bg-muted px-2 py-0.5 font-mono text-xs text-muted-foreground\\\";\\nconst PROSE: &str = \\\"prose prose-sm max-w-none text-foreground\\\";\\nconst FALLBACK_CONTENT: &str = \\\"font-sans text-sm leading-relaxed whitespace-pre-wrap\\\";\\n\\n/// Join a base class string with a consumer's extra classes.\\nfn join(base: &str, extra: &str) -> String {\\n if extra.is_empty() {\\n base.to_owned()\\n } else {\\n format!(\\\"{base} {extra}\\\")\\n }\\n}\\n\\n/// Props for [`DocsEngine`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct DocsEngineProps {\\n /// Every page the portal knows about.\\n pub pages: Vec<DocPage>,\\n /// Slug of the page on screen.\\n #[props(default)]\\n pub current_slug: Option<String>,\\n /// Called when a sidebar entry is chosen.\\n #[props(default)]\\n pub on_navigate: Option<EventHandler<String>>,\\n /// Current search text.\\n #[props(default)]\\n pub search: String,\\n /// Called as the search box changes. Absent hides the search box, as in the `.tsx`.\\n #[props(default)]\\n pub on_search: Option<EventHandler<String>>,\\n /// Render the page body. Absent falls back to preformatted text.\\n ///\\n /// The host owns markdown/MDX, exactly as in the `.tsx` — this component does layout and\\n /// navigation and deliberately ships no renderer.\\n #[props(default)]\\n pub render_content: Option<Callback<DocPage, Element>>,\\n /// Node labels. Empty means [`default_node_labels`].\\n #[props(default)]\\n pub node_labels: Vec<NodeLabel>,\\n /// Extra classes, merged onto the root.\\n #[props(default)]\\n pub class: String,\\n /// Any other root attributes.\\n #[props(extends = GlobalAttributes)]\\n pub attributes: Vec<Attribute>,\\n}\\n\\n/// The documentation portal: sidebar navigation plus a rendered page.\\n#[component]\\npub fn DocsEngine(props: DocsEngineProps) -> Element {\\n let current_page = props\\n .current_slug\\n .as_ref()\\n .and_then(|slug| props.pages.iter().find(|p| &p.slug == slug))\\n .cloned();\\n\\n let filtered = filter_pages(&props.pages, &props.search);\\n let cats = categories(&filtered);\\n\\n let owned_labels = default_node_labels();\\n let labels: &[NodeLabel] = if props.node_labels.is_empty() {\\n &owned_labels\\n } else {\\n &props.node_labels\\n };\\n\\n let searching = !props.search.trim().is_empty();\\n\\n // Read before the `if let` below consumes `current_page`.\\n let main_label = current_page\\n .as_ref()\\n .map_or_else(|| \\\"Documentation\\\".to_owned(), |p| p.title.clone());\\n\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-docs-engine\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-docs-engine\\\",\\n class: join(ROOT, &props.class),\\n ..props.attributes,\\n\\n aside { class: ASIDE, \\\"aria-label\\\": \\\"Documentation navigation\\\",\\n\\n if let Some(on_search) = props.on_search {\\n div { class: \\\"mb-4\\\",\\n label { r#for: \\\"docs-search\\\", class: \\\"sr-only\\\", \\\"Search documentation\\\" }\\n input {\\n id: \\\"docs-search\\\",\\n value: \\\"{props.search}\\\",\\n placeholder: \\\"Search docs...\\\",\\n class: SEARCH_INPUT,\\n oninput: move |e| on_search.call(e.value()),\\n }\\n }\\n }\\n\\n if cats.is_empty() && searching {\\n p { class: \\\"px-2 text-sm text-muted-foreground\\\", \\\"No results for \\\\\\\"{props.search}\\\\\\\"\\\" }\\n }\\n\\n for cat in cats.iter() {\\n div { key: \\\"{cat}\\\", class: \\\"mb-4\\\",\\n p { class: CATEGORY_HEADING, \\\"{category_label(cat)}\\\" }\\n // A real list of real buttons. The `.tsx` puts role=\\\"list\\\" on the\\n // nav and role=\\\"listitem\\\" on each button, which takes the button\\n // role away from every control here.\\n nav {\\n ul { class: \\\"contents\\\",\\n for p in filtered.iter().filter(|p| &p.category == cat) {\\n li { key: \\\"{p.slug}\\\",\\n {\\n let is_current = props.current_slug.as_deref() == Some(p.slug.as_str());\\n let slug = p.slug.clone();\\n // Hoisted: an rsx format string takes an ident or a\\n // simple expression, not an `if` block.\\n let item_class = format!(\\n \\\"{NAV_ITEM} {}\\\",\\n if is_current { NAV_ITEM_CURRENT } else { NAV_ITEM_IDLE }\\n );\\n let current = if is_current { \\\"page\\\" } else { \\\"false\\\" };\\n rsx! {\\n button {\\n class: \\\"{item_class}\\\",\\n \\\"aria-current\\\": \\\"{current}\\\",\\n onclick: move |_| {\\n if let Some(nav) = props.on_navigate {\\n nav.call(slug.clone());\\n }\\n },\\n \\\"{p.title}\\\"\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n\\n main {\\n class: MAIN,\\n \\\"aria-label\\\": \\\"{main_label}\\\",\\n\\n if let Some(page) = current_page {\\n article { class: \\\"max-w-3xl\\\",\\n header { class: \\\"mb-8 border-b border-border pb-6\\\",\\n h1 { class: PAGE_TITLE, \\\"{page.title}\\\" }\\n\\n if let Some(description) = page.description.as_ref() {\\n p { class: PAGE_DESCRIPTION, \\\"{description}\\\" }\\n }\\n\\n div { class: \\\"mt-4 flex flex-wrap items-center gap-3 text-xs text-muted-foreground\\\",\\n if let Some(version) = page.version.as_ref() {\\n span { class: \\\"font-mono\\\", \\\"v{version}\\\" }\\n }\\n if let Some(updated) = page.updated_at.as_ref() {\\n time { \\\"Updated {updated}\\\" }\\n }\\n }\\n\\n if !page.related_nodes.is_empty() {\\n div {\\n class: \\\"mt-3 flex flex-wrap gap-1.5\\\",\\n \\\"aria-label\\\": \\\"Related ecosystem nodes\\\",\\n for n in page.related_nodes.iter().copied() {\\n {\\n let label = node_label(labels, n);\\n rsx! {\\n span { key: \\\"{n}\\\", class: RELATED_NODE, title: \\\"{label}\\\", \\\"{label}\\\" }\\n }\\n }\\n }\\n }\\n }\\n }\\n\\n div { class: PROSE,\\n if let Some(render) = props.render_content {\\n {render.call(page.clone())}\\n } else {\\n pre { class: FALLBACK_CONTENT, \\\"{page.content}\\\" }\\n }\\n }\\n }\\n } else {\\n div { class: \\\"flex h-full items-center justify-center\\\",\\n p { class: \\\"text-muted-foreground\\\",\\n if props.pages.is_empty() {\\n \\\"No documentation pages found.\\\"\\n } else {\\n \\\"Select a page from the sidebar.\\\"\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n fn pages() -> Vec<DocPage> {\\n let mut a = DocPage::new(\\\"intro\\\", \\\"Getting started\\\", \\\"guides\\\", \\\"body\\\");\\n a.description = Some(\\\"How to begin\\\".to_owned());\\n a.keywords = vec![\\\"setup\\\".to_owned(), \\\"install\\\".to_owned()];\\n let b = DocPage::new(\\\"tokens\\\", \\\"Design tokens\\\", \\\"reference\\\", \\\"body\\\");\\n let c = DocPage::new(\\\"button\\\", \\\"Button\\\", \\\"reference\\\", \\\"body\\\");\\n vec![a, b, c]\\n }\\n\\n #[test]\\n fn an_empty_search_returns_everything() {\\n assert_eq!(filter_pages(&pages(), \\\"\\\").len(), 3);\\n assert_eq!(filter_pages(&pages(), \\\" \\\").len(), 3);\\n }\\n\\n #[test]\\n fn search_covers_title_description_and_keywords() {\\n assert_eq!(filter_pages(&pages(), \\\"Button\\\").len(), 1);\\n assert_eq!(filter_pages(&pages(), \\\"how to begin\\\").len(), 1);\\n assert_eq!(filter_pages(&pages(), \\\"install\\\").len(), 1);\\n }\\n\\n #[test]\\n fn search_is_case_insensitive_in_both_directions() {\\n // Query case and source case both fold.\\n assert_eq!(filter_pages(&pages(), \\\"BUTTON\\\").len(), 1);\\n assert_eq!(filter_pages(&pages(), \\\"getting STARTED\\\").len(), 1);\\n }\\n\\n #[test]\\n fn search_is_substring_not_fuzzy() {\\n // The words are present but not contiguous, so it does not match — the `.ts`\\n // uses `String.includes`, and this must behave identically or the two targets\\n // return different result sets for the same query.\\n assert_eq!(filter_pages(&pages(), \\\"getting fast\\\").len(), 0);\\n assert_eq!(filter_pages(&pages(), \\\"sartd\\\").len(), 0);\\n }\\n\\n #[test]\\n fn categories_keep_first_appearance_order() {\\n assert_eq!(categories(&pages()), vec![\\\"guides\\\", \\\"reference\\\"]);\\n }\\n\\n #[test]\\n fn categories_of_nothing_is_nothing() {\\n assert!(categories(&[]).is_empty());\\n }\\n\\n #[test]\\n fn a_category_slug_renders_with_spaces() {\\n assert_eq!(\\n category_label(\\\"getting-started-fast\\\"),\\n \\\"getting started fast\\\"\\n );\\n }\\n\\n #[test]\\n fn node_labels_fall_back_to_the_bare_number() {\\n let labels = default_node_labels();\\n assert_eq!(node_label(&labels, 2), \\\"N2 Primitives\\\");\\n assert_eq!(node_label(&labels, 99), \\\"N99\\\");\\n }\\n\\n #[test]\\n fn the_default_labels_reach_past_n10() {\\n let labels = default_node_labels();\\n assert_eq!(node_label(&labels, 11), \\\"N11 Discovery\\\");\\n assert_eq!(node_label(&labels, 12), \\\"N12 Skills\\\");\\n }\\n\\n #[test]\\n fn caller_supplied_labels_replace_the_default() {\\n let custom = vec![NodeLabel {\\n number: 2,\\n label: \\\"Second\\\".to_owned(),\\n }];\\n assert_eq!(node_label(&custom, 2), \\\"Second\\\");\\n }\\n\\n #[test]\\n fn the_nav_item_meets_the_published_touch_floor() {\\n // The .tsx ships min-h-[44px]; the system publishes 48px minimum.\\n assert!(NAV_ITEM.contains(\\\"min-h-[48px]\\\"));\\n assert!(!NAV_ITEM.contains(\\\"44px\\\"));\\n }\\n\\n #[test]\\n fn the_search_box_meets_it_too() {\\n assert!(SEARCH_INPUT.contains(\\\"min-h-[48px]\\\"));\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1212
|
+
"mzizi-empty-state": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-empty-state\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Universal branded empty state with dynamic mineral accent, contextual illustration placeholder, title, description, and optional action CTA. Every screen across the ecosystem uses this when there is no content to display — \\\"No events yet\\\", \\\"Your wallet is empty\\\", \\\"No messages\\\", \\\"Start your first lesson\\\". The empty state guides users toward the creation action for that domain.\",\"crate\":{\"name\":\"mzizi-brand\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n3-brand/mzizi-empty-state.rs\",\"type\":\"registry:rust\",\"content\":\"//! MZIZI EMPTY STATE — N3 brand, Dioxus. A Mzizi Roots component.\\n//!\\n//! The Rust sibling of `mzizi-empty-state.tsx`: the branded empty state every feed and list\\n//! shows when there is nothing in it. Same `data-slot`, same `role=\\\"status\\\"`, same compact and\\n//! full sizes, same brand-accent primary action.\\n//!\\n//! # The harness's motion preference is a prop\\n//!\\n//! [`MziziEmptyStateProps::prefers_reduced_motion`] replaces `useMziziHarness`'s\\n//! `motion.prefersReduced`, as `mzizi-update-prompt.rs` does in N7.\\n//!\\n//! # An action needs both a label and a handler, as in the `.tsx`\\n//!\\n//! The `.tsx` renders the primary button only when `onAction && actionLabel`, and the same for\\n//! the secondary. [`EmptyStateAction`] carries the two together, so the half-configured case\\n//! the `.tsx` silently drops cannot be written.\\n\\nuse dioxus::prelude::*;\\n\\n/// The component's contract, in the clause grammar of the Mzizi language's `contract … end`\\n/// block (mzizi-dev/mzizi `design/RFC-0006-contracts.md`). `mzizi-brand`'s contract suite\\n/// evaluates every clause against this component's server-rendered markup and fails on any\\n/// clause it cannot evaluate.\\npub const CONTRACT: &str = r#\\\"contract\\n slot is \\\"mzizi-empty-state\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-empty-state\\\"\\n role is \\\"status\\\"\\n every empty_state_density padding not_empty\\n button \\\"Create\\\" min_height 48\\n button \\\"Learn more\\\" min_height 48\\nend\\\"#;\\n\\n/// Full or compact spacing. `compact` is the `.tsx`'s boolean prop.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]\\npub enum EmptyStateDensity {\\n /// Page-level empty state.\\n #[default]\\n Full,\\n /// Inline empty state inside a card.\\n Compact,\\n}\\n\\nimpl EmptyStateDensity {\\n /// Every density, for exhaustive checks.\\n pub const ALL: [Self; 2] = [Self::Full, Self::Compact];\\n\\n /// Outer padding.\\n #[must_use]\\n pub const fn padding(self) -> &'static str {\\n match self {\\n Self::Full => \\\"px-6 py-16\\\",\\n Self::Compact => \\\"px-4 py-8\\\",\\n }\\n }\\n\\n /// Icon well size and spacing.\\n #[must_use]\\n pub const fn icon(self) -> &'static str {\\n match self {\\n Self::Full => \\\"mb-4 size-16 text-2xl\\\",\\n Self::Compact => \\\"mb-3 size-12 text-xl\\\",\\n }\\n }\\n\\n /// Title size.\\n #[must_use]\\n pub const fn title(self) -> &'static str {\\n match self {\\n Self::Full => \\\"text-base\\\",\\n Self::Compact => \\\"text-sm\\\",\\n }\\n }\\n\\n /// Description size and spacing.\\n #[must_use]\\n pub const fn description(self) -> &'static str {\\n match self {\\n Self::Full => \\\"mt-2 text-sm\\\",\\n Self::Compact => \\\"mt-1 text-xs\\\",\\n }\\n }\\n\\n /// Space above the actions.\\n #[must_use]\\n pub const fn actions(self) -> &'static str {\\n match self {\\n Self::Full => \\\"mt-6\\\",\\n Self::Compact => \\\"mt-4\\\",\\n }\\n }\\n}\\n\\n/// A labelled action button.\\n#[derive(Clone, PartialEq)]\\npub struct EmptyStateAction {\\n /// Button label.\\n pub label: String,\\n /// Fired on press.\\n pub on_press: EventHandler<()>,\\n}\\n\\n/// The entrance animation, empty when motion is reduced — the `.tsx`'s `animStyle`, with the\\n/// harness's 200ms decelerate curve.\\n#[must_use]\\npub fn entrance_style(prefers_reduced_motion: bool) -> &'static str {\\n if prefers_reduced_motion {\\n \\\"\\\"\\n } else {\\n \\\"animation: nyuchi-fade-slide-up 200ms cubic-bezier(0, 0, 0.2, 1) both;\\\"\\n }\\n}\\n\\nconst PRIMARY: &str = \\\"h-12 rounded-full bg-[var(--brand-accent,var(--color-malachite,#64FFDA))] px-6 text-[13px] font-medium text-[var(--brand-accent-foreground,#0A0A0A)] transition-opacity hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary,#00B0FF)]\\\";\\nconst SECONDARY: &str = \\\"h-12 rounded-full border border-border bg-muted px-6 text-[13px] font-medium text-foreground transition-opacity hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary,#00B0FF)]\\\";\\n\\n/// Props for [`MziziEmptyState`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziEmptyStateProps {\\n /// Illustration or glyph above the title.\\n #[props(default)]\\n pub icon: Option<Element>,\\n /// What is empty.\\n #[props(into)]\\n pub title: String,\\n /// What the user can do about it.\\n #[props(default)]\\n pub description: Option<String>,\\n /// Primary call to action.\\n #[props(default)]\\n pub action: Option<EmptyStateAction>,\\n /// Secondary action.\\n #[props(default)]\\n pub secondary: Option<EmptyStateAction>,\\n /// Full or compact spacing.\\n #[props(default)]\\n pub density: EmptyStateDensity,\\n /// Replaces the harness's `motion.prefersReduced`.\\n #[props(default)]\\n pub prefers_reduced_motion: bool,\\n /// Extra classes, appended last.\\n #[props(default)]\\n pub class: String,\\n}\\n\\n/// The branded empty state.\\n#[component]\\npub fn MziziEmptyState(props: MziziEmptyStateProps) -> Element {\\n let d = props.density;\\n let root = if props.class.is_empty() {\\n format!(\\n \\\"flex flex-col items-center justify-center text-center {}\\\",\\n d.padding()\\n )\\n } else {\\n format!(\\n \\\"flex flex-col items-center justify-center text-center {} {}\\\",\\n d.padding(),\\n props.class\\n )\\n };\\n let has_actions = props.action.is_some() || props.secondary.is_some();\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-empty-state\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-empty-state\\\",\\n role: \\\"status\\\",\\n style: entrance_style(props.prefers_reduced_motion),\\n class: \\\"{root}\\\",\\n if let Some(icon) = &props.icon {\\n div { class: \\\"flex items-center justify-center rounded-full bg-[var(--brand-accent,var(--color-malachite,#64FFDA))]/10 {d.icon()}\\\",\\n {icon}\\n }\\n }\\n h3 { class: \\\"font-semibold text-foreground {d.title()}\\\", style: \\\"font-family: var(--font-serif);\\\",\\n \\\"{props.title}\\\"\\n }\\n if let Some(description) = &props.description {\\n p { class: \\\"max-w-[280px] leading-relaxed text-muted-foreground {d.description()}\\\", \\\"{description}\\\" }\\n }\\n if has_actions {\\n div { class: \\\"flex items-center gap-2 {d.actions()}\\\",\\n if let Some(action) = &props.action {\\n button {\\n r#type: \\\"button\\\",\\n class: PRIMARY,\\n onclick: {\\n let on_press = action.on_press;\\n move |_| on_press.call(())\\n },\\n \\\"{action.label}\\\"\\n }\\n }\\n if let Some(secondary) = &props.secondary {\\n button {\\n r#type: \\\"button\\\",\\n class: SECONDARY,\\n onclick: {\\n let on_press = secondary.on_press;\\n move |_| on_press.call(())\\n },\\n \\\"{secondary.label}\\\"\\n }\\n }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn compact_is_tighter_than_full() {\\n assert_eq!(EmptyStateDensity::Full.padding(), \\\"px-6 py-16\\\");\\n assert_eq!(EmptyStateDensity::Compact.padding(), \\\"px-4 py-8\\\");\\n }\\n\\n #[test]\\n fn both_action_buttons_meet_the_touch_floor() {\\n assert!(PRIMARY.split_whitespace().any(|c| c == \\\"h-12\\\"));\\n assert!(SECONDARY.split_whitespace().any(|c| c == \\\"h-12\\\"));\\n }\\n}\\n\"}],\"contract\":\"contract\\n slot is \\\"mzizi-empty-state\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-empty-state\\\"\\n role is \\\"status\\\"\\n every empty_state_density padding not_empty\\n button \\\"Create\\\" min_height 48\\n button \\\"Learn more\\\" min_height 48\\nend\"}",
|
|
1213
|
+
"mzizi-error-tracker": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-error-tracker\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"Structured error collection, deduplication, and blast radius analysis. Catches errors from L5 error boundaries, enriches them with component backlinks (data-portal), calculates which mini-apps and layers are affected. Competitors: Sentry, Datadog Error Tracking.\",\"crate\":{\"name\":\"mzizi-assurance\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n8-assurance/mzizi-error-tracker.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N8 assurance — structured error collection, deduplication and blast radius.\\n//!\\n//! The Rust implementation of `mzizi-error-tracker`.\\n//!\\n//! # What this owns\\n//!\\n//! Classification, deduplication, eviction and auto-resolution — everything that\\n//! decides *what an error means* and *which errors are still live*. Not the DOM\\n//! walk that discovers a blast radius, not the clock, not the delivery of a\\n//! report; those are the host's, as everywhere else in this node.\\n//!\\n//! # Three defects in the TypeScript, addressed rather than ported\\n//!\\n//! **1. `autoResolveMinutes` was documented, defaulted, and never read.** The\\n//! `.ts` declares it, `Required<>` fills it with 60, and no code path consults\\n//! it — so every error stayed unresolved forever and \\\"unresolved errors\\\" grew\\n//! without bound as a dashboard number. A configuration option that does nothing\\n//! is worse than an absent one, because it is *believed*. Implemented here as\\n//! [`ErrorLog::auto_resolve`], which the host calls with its own clock.\\n//!\\n//! **2. Eviction sorted the entire map on every insert past the cap.** At 500\\n//! errors that is a full `O(n log n)` sort per tracked error — and the cap is\\n//! only exceeded during an error storm, so the slow path engaged at precisely the\\n//! worst moment. Finding the oldest entry is a single linear scan.\\n//!\\n//! **3. Undeduplicated ids collided.** With `dedup: false` the key is\\n//! `Date.now().toString()`, unique only if no two errors arrive in the same\\n//! millisecond — which an error storm violates by construction. Ids are supplied\\n//! here for the same reason they are in the alert engine and the exporter.\\n//!\\n//! # One thing left alone, deliberately\\n//!\\n//! [`classify_severity`] tests the error *message* for `\\\"TypeError\\\"`, which is\\n//! usually the error's `name` rather than anything in its message — so that\\n//! branch rarely fires and the neighbouring `\\\"Cannot read\\\"` test is what actually\\n//! catches those. It is preserved because changing it would silently reclassify\\n//! live errors, which is a product decision rather than a porting one. The\\n//! observation is recorded here instead of being quietly fixed or quietly\\n//! carried.\\n\\nuse std::collections::BTreeMap;\\n\\n/// How bad an error is.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum Severity {\\n /// Noise, or a well-handled edge.\\n Low,\\n /// Worth looking at.\\n Medium,\\n /// Degrades something users touch.\\n High,\\n /// A core guarantee broke.\\n Critical,\\n}\\n\\nimpl Severity {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Low => \\\"low\\\",\\n Self::Medium => \\\"medium\\\",\\n Self::High => \\\"high\\\",\\n Self::Critical => \\\"critical\\\",\\n }\\n }\\n}\\n\\n/// Where an error happened, as much as the host can say.\\n#[derive(Debug, Clone, Default)]\\npub struct ErrorContext {\\n /// The component's `data-slot` name.\\n pub component_name: Option<String>,\\n /// Which helix node it belongs to.\\n pub node: Option<u32>,\\n /// Which mini-app was running.\\n pub mini_app: Option<String>,\\n /// The path the user was on.\\n pub url: Option<String>,\\n /// Components in the same render tree that may be affected.\\n ///\\n /// Supplied, not discovered: the `.ts` finds these by walking\\n /// `document.querySelectorAll(\\\"[data-slot]\\\")`, and a Worker or native shell\\n /// has no document.\\n pub blast_radius: Vec<String>,\\n}\\n\\n/// One deduplicated error, with its recurrence history.\\n///\\n/// `PartialEq` but not `Eq`: the timestamps are `f64`, and floats have no total\\n/// equality. Deriving `Eq` here would be claiming a property the type does not\\n/// have.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct TrackedError {\\n /// Dedup key or supplied id.\\n pub id: String,\\n /// The error message.\\n pub message: String,\\n /// Stack trace, when the host captured one.\\n pub stack: Option<String>,\\n /// The component blamed.\\n pub component_name: Option<String>,\\n /// Which helix node.\\n pub node: Option<u32>,\\n /// Which mini-app.\\n pub mini_app: Option<String>,\\n /// The path the user was on.\\n pub url: String,\\n /// How many times this has been seen.\\n pub count: u32,\\n /// First occurrence, milliseconds since the Unix epoch.\\n pub first_seen_ms: f64,\\n /// Most recent occurrence.\\n pub last_seen_ms: f64,\\n /// Components that may be affected.\\n pub blast_radius: Vec<String>,\\n /// How bad.\\n pub severity: Severity,\\n /// Whether it is considered settled.\\n pub resolved: bool,\\n}\\n\\n/// Classify an error's severity.\\n///\\n/// Pure, and ordered so the strongest signal wins: a broken token or safety node\\n/// is critical regardless of anything else, because N1 and N4 are core\\n/// guarantees — if design values or safety gates fail, everything downstream is\\n/// already wrong.\\n#[must_use]\\npub fn classify_severity(message: &str, node: Option<u32>, blast_radius: usize) -> Severity {\\n // N1 tokens and N4 safety are core guarantees. Their failure is never minor.\\n if node == Some(1) || node == Some(4) {\\n return Severity::Critical;\\n }\\n // N7 is the shell: the product stops holding together.\\n if node == Some(7) {\\n return Severity::High;\\n }\\n if blast_radius > 10 {\\n return Severity::High;\\n }\\n if message.contains(\\\"TypeError\\\") || message.contains(\\\"Cannot read\\\") {\\n return Severity::Medium;\\n }\\n Severity::Low\\n}\\n\\n/// Configuration for a log.\\n#[derive(Debug, Clone)]\\npub struct ErrorLogConfig {\\n /// How many distinct errors to retain.\\n pub max_errors: usize,\\n /// Whether to group recurrences of the same message + component.\\n pub dedup: bool,\\n /// Minutes without recurrence after which an error auto-resolves.\\n ///\\n /// Actually consulted here — see the module docs for why that is worth\\n /// saying out loud.\\n pub auto_resolve_minutes: f64,\\n}\\n\\nimpl Default for ErrorLogConfig {\\n fn default() -> Self {\\n Self {\\n max_errors: 500,\\n dedup: true,\\n auto_resolve_minutes: 60.0,\\n }\\n }\\n}\\n\\n/// What tracking an error did.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum Tracked {\\n /// A new distinct error was recorded.\\n New,\\n /// An existing error recurred; its count went up.\\n Recurrence,\\n}\\n\\n/// The set of errors a host is holding.\\n#[derive(Debug, Clone, Default)]\\npub struct ErrorLog {\\n config: ErrorLogConfig,\\n errors: BTreeMap<String, TrackedError>,\\n}\\n\\nimpl ErrorLog {\\n /// A log with the given configuration.\\n #[must_use]\\n pub fn new(config: ErrorLogConfig) -> Self {\\n Self {\\n config,\\n errors: BTreeMap::new(),\\n }\\n }\\n\\n /// The dedup key for a message and component.\\n ///\\n /// When dedup is off the caller's `id` is used instead — the `.ts` reaches\\n /// for `Date.now()` there, which collides during exactly the error storms\\n /// that make dedup worth turning off.\\n #[must_use]\\n pub fn dedup_key(message: &str, component_name: Option<&str>) -> String {\\n format!(\\\"{message}:{}\\\", component_name.unwrap_or(\\\"unknown\\\"))\\n }\\n\\n /// Record an occurrence.\\n ///\\n /// `id` is used only when dedup is off. `now_ms` is supplied because a core\\n /// has no clock.\\n pub fn track(\\n &mut self,\\n message: impl Into<String>,\\n stack: Option<String>,\\n context: &ErrorContext,\\n id: impl Into<String>,\\n now_ms: f64,\\n ) -> (Tracked, &TrackedError) {\\n let message = message.into();\\n let key = if self.config.dedup {\\n Self::dedup_key(&message, context.component_name.as_deref())\\n } else {\\n id.into()\\n };\\n\\n let severity = classify_severity(&message, context.node, context.blast_radius.len());\\n\\n // The mutation and the returned reference are separated deliberately.\\n // Returning `&TrackedError` out of both branches of a `&mut self` method\\n // needs borrow-checker support Rust does not have yet (polonius), so the\\n // borrow is taken once, at the end, after every mutation is finished.\\n let outcome = if let Some(existing) = self.errors.get_mut(&key) {\\n existing.count += 1;\\n existing.last_seen_ms = now_ms;\\n // A recurrence un-resolves: an error that came back was not settled.\\n existing.resolved = false;\\n existing.severity = severity;\\n Tracked::Recurrence\\n } else {\\n self.errors.insert(\\n key.clone(),\\n TrackedError {\\n id: key.clone(),\\n message,\\n stack,\\n component_name: context.component_name.clone(),\\n node: context.node,\\n mini_app: context.mini_app.clone(),\\n url: context.url.clone().unwrap_or_default(),\\n count: 1,\\n first_seen_ms: now_ms,\\n last_seen_ms: now_ms,\\n blast_radius: context.blast_radius.clone(),\\n severity,\\n resolved: false,\\n },\\n );\\n self.evict_if_over_cap(&key);\\n Tracked::New\\n };\\n\\n (\\n outcome,\\n self.errors\\n .get(&key)\\n .expect(\\\"just tracked, and eviction never removes the new key\\\"),\\n )\\n }\\n\\n /// Drop the least recently seen error when over the cap.\\n ///\\n /// A single linear scan rather than sorting the whole map. The `.ts` sorts on\\n /// every insert past the cap, which means the expensive path runs during an\\n /// error storm — the one moment the tracker should be cheapest.\\n ///\\n /// The just-inserted key is never the victim, even if the cap is 1: evicting\\n /// what you were asked to record is not eviction, it is a silent drop.\\n fn evict_if_over_cap(&mut self, keep: &str) {\\n if self.errors.len() <= self.config.max_errors {\\n return;\\n }\\n let oldest = self\\n .errors\\n .iter()\\n .filter(|(k, _)| k.as_str() != keep)\\n .min_by(|a, b| {\\n a.1.last_seen_ms\\n .partial_cmp(&b.1.last_seen_ms)\\n .unwrap_or(std::cmp::Ordering::Equal)\\n })\\n .map(|(k, _)| k.clone());\\n if let Some(key) = oldest {\\n self.errors.remove(&key);\\n }\\n }\\n\\n /// Resolve everything untouched for longer than the configured window.\\n ///\\n /// Returns how many were resolved. This is the option the `.ts` declared and\\n /// never implemented.\\n pub fn auto_resolve(&mut self, now_ms: f64) -> usize {\\n let window_ms = self.config.auto_resolve_minutes * 60_000.0;\\n let mut resolved = 0;\\n for error in self.errors.values_mut() {\\n if !error.resolved && now_ms - error.last_seen_ms >= window_ms {\\n error.resolved = true;\\n resolved += 1;\\n }\\n }\\n resolved\\n }\\n\\n /// Mark one error resolved. Returns false if the id is unknown.\\n pub fn resolve(&mut self, id: &str) -> bool {\\n match self.errors.get_mut(id) {\\n Some(error) => {\\n error.resolved = true;\\n true\\n }\\n None => false,\\n }\\n }\\n\\n /// Every error held.\\n #[must_use]\\n pub fn all(&self) -> Vec<&TrackedError> {\\n self.errors.values().collect()\\n }\\n\\n /// Every error not yet resolved.\\n #[must_use]\\n pub fn unresolved(&self) -> Vec<&TrackedError> {\\n self.errors.values().filter(|e| !e.resolved).collect()\\n }\\n\\n /// How many distinct errors are held.\\n #[must_use]\\n pub fn len(&self) -> usize {\\n self.errors.len()\\n }\\n\\n /// Whether the log is empty.\\n #[must_use]\\n pub fn is_empty(&self) -> bool {\\n self.errors.is_empty()\\n }\\n\\n /// Forget everything.\\n pub fn clear(&mut self) {\\n self.errors.clear();\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1214
|
+
"mzizi-escalation-card": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-escalation-card\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"The UCP Embedded Checkout Protocol \\\"requires escalation\\\" mini-card: when an agentic flow needs a human choice, the agent pauses and renders this inline, then resumes on selection. Keeps a decision point in the conversation instead of handing off to a browser.\",\"crate\":{\"name\":\"mzizi-brand\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n3-brand/mzizi-escalation-card.rs\",\"type\":\"registry:rust\",\"content\":\"//! MZIZI ESCALATION CARD — N3 brand, Dioxus. A Mzizi Roots component.\\n//!\\n//! The Rust sibling of `mzizi-escalation-card.tsx`: the \\\"requires escalation\\\" card an agent\\n//! renders when a flow needs a human choice. The agent pauses, the person picks an option, the\\n//! agent resumes. Same `data-slot`, same `role=\\\"group\\\"` named by the title, same 48px option\\n//! buttons.\\n//!\\n//! # The host owns the pending state\\n//!\\n//! The `.tsx` keeps `pendingId` in React state, awaits `onChoose`, and clears it in a\\n//! `finally`. A Dioxus [`EventHandler`] returns nothing to await, so this port does not\\n//! pretend to: [`MziziEscalationCardProps::pending_id`] is a controlled prop. The host sets it\\n//! when it starts resuming the agent and clears it when the resume settles. That also lets a\\n//! host that re-renders from agent state (a server, a Worker, a replay) show the pending\\n//! option without any client state at all, which the `.tsx` cannot do.\\n//!\\n//! While `pending_id` is set every option is disabled and the chosen one reads \\\"…\\\", as in the\\n//! `.tsx`. [`is_choosable`] is that guard.\\n//!\\n//! # No logging dependency\\n//!\\n//! The `.tsx` logs `escalation_choose` through the harness. Logging is the host's job here:\\n//! it receives the id in `on_choose` and can record it where it records everything else.\\n\\nuse dioxus::prelude::*;\\n\\n/// The component's contract, in the clause grammar of the Mzizi language's `contract … end`\\n/// block (mzizi-dev/mzizi `design/RFC-0006-contracts.md`). `mzizi-brand`'s contract suite\\n/// evaluates every clause against this component's server-rendered markup and fails on any\\n/// clause it cannot evaluate.\\npub const CONTRACT: &str = r#\\\"contract\\n slot is \\\"mzizi-escalation-card\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-escalation-card\\\"\\n role is \\\"group\\\"\\n label not_empty\\n button \\\"Approve\\\" min_height 48\\n button \\\"Decline\\\" min_height 48\\n when pending shows span \\\"…\\\"\\nend\\\"#;\\n\\n/// One option the person can choose.\\n#[derive(Clone, Debug, PartialEq)]\\npub struct EscalationOption {\\n /// Stable id, passed back to `on_choose`.\\n pub id: String,\\n /// Button label.\\n pub label: String,\\n /// Muted line under the label.\\n pub description: Option<String>,\\n}\\n\\n/// Whether an option can be chosen now: only while nothing is pending, as the `.tsx`'s\\n/// `if (pendingId) return` guard.\\n#[must_use]\\npub fn is_choosable(pending_id: Option<&str>) -> bool {\\n pending_id.is_none()\\n}\\n\\n/// The label an option shows: \\\"…\\\" for the pending one, its own label otherwise.\\n#[must_use]\\npub fn option_label<'a>(option: &'a EscalationOption, pending_id: Option<&str>) -> &'a str {\\n if pending_id == Some(option.id.as_str()) {\\n \\\"…\\\"\\n } else {\\n &option.label\\n }\\n}\\n\\n/// The entrance animation, empty when motion is reduced — the `.tsx`'s `animStyle`, with the\\n/// harness's 200ms decelerate curve.\\n#[must_use]\\npub fn entrance_style(prefers_reduced_motion: bool) -> &'static str {\\n if prefers_reduced_motion {\\n \\\"\\\"\\n } else {\\n \\\"animation: nyuchi-fade-slide-up 200ms cubic-bezier(0, 0, 0.2, 1) both;\\\"\\n }\\n}\\n\\nconst ROOT: &str = \\\"w-full max-w-sm rounded-[var(--radius-xl,17px)] border border-border bg-[var(--card)] p-6 text-[var(--card-foreground)] shadow-[var(--shadow-lg)]\\\";\\nconst OPTION: &str = \\\"flex min-h-[48px] flex-col items-start justify-center rounded-[var(--radius,10px)] border border-border bg-[var(--background)] px-4 py-2 text-left text-sm transition-colors hover:bg-[var(--muted)] disabled:opacity-60 focus-visible:ring-2 focus-visible:ring-[var(--brand-accent,var(--ring))] focus-visible:outline-none\\\";\\n\\n/// Props for [`MziziEscalationCard`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziEscalationCardProps {\\n /// What the agent needs decided; also the group's accessible name.\\n #[props(into)]\\n pub title: String,\\n /// Supporting prompt.\\n #[props(default)]\\n pub prompt: Option<String>,\\n /// The choices.\\n pub options: Vec<EscalationOption>,\\n /// Fired with the chosen option's id.\\n pub on_choose: EventHandler<String>,\\n /// The option being resumed, set by the host while the agent resumes.\\n #[props(default)]\\n pub pending_id: Option<String>,\\n /// Replaces the harness's `motion.prefersReduced`.\\n #[props(default)]\\n pub prefers_reduced_motion: bool,\\n /// Extra classes, appended last.\\n #[props(default)]\\n pub class: String,\\n}\\n\\n/// A card asking a person to choose, so a paused agent can resume.\\n#[component]\\npub fn MziziEscalationCard(props: MziziEscalationCardProps) -> Element {\\n let root = if props.class.is_empty() {\\n ROOT.to_owned()\\n } else {\\n format!(\\\"{ROOT} {}\\\", props.class)\\n };\\n let pending = props.pending_id.clone();\\n let disabled = !is_choosable(pending.as_deref());\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-escalation-card\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-escalation-card\\\",\\n role: \\\"group\\\",\\n \\\"aria-label\\\": \\\"{props.title}\\\",\\n \\\"aria-busy\\\": if disabled { \\\"true\\\" } else { \\\"false\\\" },\\n style: entrance_style(props.prefers_reduced_motion),\\n class: \\\"{root}\\\",\\n header { class: \\\"mb-4 space-y-1\\\",\\n h2 { class: \\\"text-base font-semibold tracking-tight\\\", \\\"{props.title}\\\" }\\n if let Some(prompt) = &props.prompt {\\n p { class: \\\"text-sm text-[var(--muted-foreground)]\\\", \\\"{prompt}\\\" }\\n }\\n }\\n div { class: \\\"grid gap-2\\\",\\n for option in props.options.iter() {\\n button {\\n key: \\\"{option.id}\\\",\\n r#type: \\\"button\\\",\\n disabled,\\n class: OPTION,\\n onclick: {\\n let id = option.id.clone();\\n let on_choose = props.on_choose;\\n let pending = pending.clone();\\n move |_| {\\n if is_choosable(pending.as_deref()) {\\n on_choose.call(id.clone());\\n }\\n }\\n },\\n span { class: \\\"font-medium\\\", {option_label(option, pending.as_deref())} }\\n if let Some(description) = &option.description {\\n span { class: \\\"text-xs text-[var(--muted-foreground)]\\\", \\\"{description}\\\" }\\n }\\n }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n fn option(id: &str) -> EscalationOption {\\n EscalationOption {\\n id: id.into(),\\n label: id.to_uppercase(),\\n description: None,\\n }\\n }\\n\\n #[test]\\n fn nothing_is_choosable_while_an_option_is_pending() {\\n assert!(is_choosable(None));\\n assert!(!is_choosable(Some(\\\"approve\\\")));\\n }\\n\\n #[test]\\n fn only_the_pending_option_reads_as_pending() {\\n let approve = option(\\\"approve\\\");\\n let decline = option(\\\"decline\\\");\\n assert_eq!(option_label(&approve, Some(\\\"approve\\\")), \\\"…\\\");\\n assert_eq!(option_label(&decline, Some(\\\"approve\\\")), \\\"DECLINE\\\");\\n assert_eq!(option_label(&approve, None), \\\"APPROVE\\\");\\n }\\n}\\n\"}],\"contract\":\"contract\\n slot is \\\"mzizi-escalation-card\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-escalation-card\\\"\\n role is \\\"group\\\"\\n label not_empty\\n button \\\"Approve\\\" min_height 48\\n button \\\"Decline\\\" min_height 48\\n when pending shows span \\\"…\\\"\\nend\"}",
|
|
1215
|
+
"mzizi-footer": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-footer\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Standardized footer for all Nyuchi ecosystem apps with mineral color dots.\",\"crate\":{\"name\":\"mzizi-shell\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n7-shell/mzizi-footer.rs\",\"type\":\"registry:rust\",\"content\":\"//! NYUCHI FOOTER — N7 shell, Dioxus.\\n//!\\n//! The Rust sibling of `mzizi-footer.tsx`: enterprise ecosystem footer, sharing its contract —\\n//! same `data-slot`, same default link sections.\\n//!\\n//! # The year is a prop, not computed in render\\n//!\\n//! The `.tsx` calls `new Date().getFullYear()` directly inside a \\\"use client\\\" component's\\n//! render — a hydration hazard if server and client disagree on the year (a real, if rare,\\n//! edge case at a year boundary or clock drift), the same class of defect\\n//! `mzizi-docs-engine`'s `updated_at` formatting had. Taking `year` as a prop removes it.\\n//!\\n//! **The `.tsx` sibling still has this.**\\n\\nuse dioxus::prelude::*;\\n\\n/// A single footer link.\\n#[derive(Clone, Debug, PartialEq)]\\npub struct FooterLink {\\n /// Display label.\\n pub label: String,\\n /// Link target.\\n pub href: String,\\n /// Whether this opens in a new tab with `rel=\\\"noopener noreferrer\\\"`.\\n pub external: bool,\\n}\\n\\n/// A titled group of footer links.\\n#[derive(Clone, Debug, PartialEq)]\\npub struct FooterSection {\\n /// Section heading.\\n pub title: String,\\n /// Links under this section.\\n pub links: Vec<FooterLink>,\\n}\\n\\nfn link(label: &str, href: &str, external: bool) -> FooterLink {\\n FooterLink {\\n label: label.into(),\\n href: href.into(),\\n external,\\n }\\n}\\n\\n/// Default footer sections (Nyuchi ecosystem). Same content as the `.tsx`.\\npub fn default_sections() -> Vec<FooterSection> {\\n vec![\\n FooterSection {\\n title: \\\"Platform\\\".into(),\\n links: vec![\\n link(\\\"nhimbe\\\", \\\"/nhimbe\\\", false),\\n link(\\\"Bush Trade\\\", \\\"/bushtrade\\\", false),\\n link(\\\"Shamwari\\\", \\\"/shamwari\\\", false),\\n link(\\\"Campfire\\\", \\\"/campfire\\\", false),\\n ],\\n },\\n FooterSection {\\n title: \\\"Developers\\\".into(),\\n links: vec![\\n link(\\\"Components\\\", \\\"/components\\\", false),\\n link(\\\"API\\\", \\\"/api-docs\\\", false),\\n link(\\\"Architecture\\\", \\\"/architecture\\\", false),\\n link(\\\"GitHub\\\", \\\"https://github.com/nyuchi\\\", true),\\n ],\\n },\\n FooterSection {\\n title: \\\"Company\\\".into(),\\n links: vec![\\n link(\\\"About Nyuchi\\\", \\\"/about\\\", false),\\n link(\\\"Brand\\\", \\\"/brand\\\", false),\\n link(\\\"Ubuntu Philosophy\\\", \\\"/ubuntu\\\", false),\\n link(\\\"Privacy\\\", \\\"/privacy\\\", false),\\n ],\\n },\\n ]\\n}\\n\\nconst LINK: &str = \\\"text-sm text-muted-foreground transition-colors \\\\\\n hover:text-[var(--brand-accent,var(--color-primary,#00B0FF))] \\\\\\n flex min-h-[48px] items-center rounded-[var(--radius-inner,7px)] \\\\\\n focus-visible:outline-[length:var(--focusRing-width,2px)] \\\\\\n focus-visible:outline-[var(--color-primary)] \\\\\\n focus-visible:outline-offset-[var(--focusRing-offset,2px)]\\\";\\n\\n/// Props for [`MziziFooter`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziFooterProps {\\n /// Link sections; defaults to the ecosystem's own pages.\\n #[props(default = default_sections())]\\n pub sections: Vec<FooterSection>,\\n /// Copyright line company name.\\n #[props(default = \\\"Nyuchi Africa\\\".to_string())]\\n pub company_name: String,\\n /// Tagline shown under the wordmark.\\n #[props(default = \\\"I am because we are.\\\".to_string())]\\n pub tagline: String,\\n /// Whether to show the mineral accent strip along the top edge.\\n #[props(default = true)]\\n pub show_mineral_strip: bool,\\n /// The current year, supplied by the host rather than computed with\\n /// `Date.now()` inside render — see the module docs above.\\n pub year: i32,\\n}\\n\\n/// Enterprise ecosystem footer with link sections and a copyright line.\\n#[component]\\npub fn MziziFooter(props: MziziFooterProps) -> Element {\\n rsx! {\\n footer {\\n \\\"data-slot\\\": \\\"mzizi-footer\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-footer\\\",\\n role: \\\"contentinfo\\\",\\n class: \\\"border-t border-border bg-card\\\",\\n\\n if props.show_mineral_strip {\\n div { class: \\\"h-[3px] w-full\\\" }\\n }\\n\\n div { class: \\\"mx-auto max-w-7xl px-5 py-10\\\",\\n div { class: \\\"grid grid-cols-2 gap-8 md:grid-cols-3 lg:grid-cols-4\\\",\\n div { class: \\\"col-span-2 md:col-span-1\\\",\\n p { class: \\\"mt-3 font-sans text-sm text-muted-foreground italic\\\", \\\"{props.tagline}\\\" }\\n }\\n for section in props.sections.iter() {\\n div {\\n key: \\\"{section.title}\\\",\\n h4 { class: \\\"text-xs font-semibold tracking-wider text-muted-foreground uppercase\\\", \\\"{section.title}\\\" }\\n nav { class: \\\"mt-3 flex flex-col gap-2\\\", \\\"aria-label\\\": \\\"{section.title}\\\",\\n for l in section.links.iter() {\\n a {\\n key: \\\"{l.href}\\\",\\n href: \\\"{l.href}\\\",\\n class: \\\"{LINK}\\\",\\n \\\"{l.label}\\\"\\n }\\n }\\n }\\n }\\n }\\n }\\n div { class: \\\"mt-10 flex flex-col items-center justify-between gap-2 border-t border-border pt-6 md:flex-row\\\",\\n p { class: \\\"text-xs text-muted-foreground\\\", \\\"\\\\u{00A9} {props.year} {props.company_name}. All rights reserved.\\\" }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn default_sections_match_the_three_ecosystem_groups() {\\n let sections = default_sections();\\n assert_eq!(sections.len(), 3);\\n assert_eq!(sections[0].title, \\\"Platform\\\");\\n assert_eq!(sections[1].title, \\\"Developers\\\");\\n assert_eq!(sections[2].title, \\\"Company\\\");\\n }\\n\\n #[test]\\n fn github_link_is_external() {\\n let sections = default_sections();\\n let github = sections[1]\\n .links\\n .iter()\\n .find(|l| l.label == \\\"GitHub\\\")\\n .unwrap();\\n assert!(github.external);\\n assert_eq!(github.href, \\\"https://github.com/nyuchi\\\");\\n }\\n\\n #[test]\\n fn internal_links_are_not_external() {\\n let sections = default_sections();\\n assert!(!sections[0].links[0].external);\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1216
|
+
"mzizi-fundi": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-fundi\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"Fundi — the self-healing intelligence engine. Swahili for fixer/mechanic/technician. N9 acts on what N8 observes. Built in Rust, deployed as a Cloudflare Worker (workers-rs crate, compiled to WebAssembly). NOT a Cloudflare Worker (Rust/WASM) — Fundi is independent of the database vendor. Reads fundi_issues and observability_events from Supabase via REST API. Classifies failure patterns, deduplicates against open GitHub issues, creates structured remediation tickets. Triggered by Cloudflare Cron Triggers and Cloudflare Queues.\",\"crate\":{\"name\":\"mzizi-fundi\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n9-fundi/mzizi-fundi.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N9 fundi — the healing decision engine.\\n//!\\n//! The Rust implementation of `mzizi-fundi`. N8 assurance observes and finds\\n//! problems; N9 receives those observations and decides what to do. Two rungs,\\n//! one loop, and this is the hinge between them.\\n//!\\n//! # What this owns\\n//!\\n//! The decision: diagnostic in, plan out. Not the execution — a remediation\\n//! action clears a cache, opens a circuit or pages somebody, all of which is I/O\\n//! and all of which differs per host. [`plan_outcome`] folds the results the host\\n//! reports back.\\n//!\\n//! # Six defects in the TypeScript, fixed here\\n//!\\n//! **1. \\\"Requires human approval\\\" did not stop anything.** `FundiProvider`\\n//! defaults `autoHeal = true`, and the gate reads `if (plan.requiresHumanApproval\\n//! && !autoHeal)` — so the flag meaning \\\"a person must look at this\\\" is overridden\\n//! by a convenience flag that is on unless somebody turns it off. A systemic-outage\\n//! plan disables non-critical features automatically while declaring that it should\\n//! not. [`HealingPlan::approval`] is a state a host must clear, not a boolean it can\\n//! out-vote, and [`HealingPlan::may_execute`] is the only thing that answers.\\n//!\\n//! **2. \\\"Never auto-fix auth\\\" was skipped exactly when it mattered most.** The\\n//! `errorType === \\\"auth\\\"` branch sits inside the `else` that handles ISOLATED\\n//! failures. A partial or systemic auth failure never reaches it — it gets\\n//! `degrade-feature` and `reroute` instead. The one rule the file states as\\n//! inviolable applied only to the narrowest case. Auth is checked first here,\\n//! before the blast radius is consulted at all.\\n//!\\n//! **3. Chain and crypto failures had the same hole.** Same `else`, same\\n//! consequence: a systemic chain failure is rerouted rather than falling back to\\n//! the Web2 path.\\n//!\\n//! **4. Nothing handled \\\"we could not determine the blast radius\\\".** The union is\\n//! `isolated | partial | systemic`, so an unknown radius has no representation and\\n//! the `else` treats it as isolated — the calmest response, chosen when the least\\n//! is known. `mzizi-chaos` now answers `unknown` when nothing was probed, and the\\n//! two halves of one loop have to share a vocabulary. [`BlastRadius::Unknown`]\\n//! requires approval and escalates.\\n//!\\n//! **5. The reroute target depended on probe ordering.** `healthyNodes[0]` takes\\n//! whichever healthy probe happened to come back first, so two runs over the same\\n//! set can reroute to different targets and neither is reproducible. Sorted by\\n//! name.\\n//!\\n//! **6. The plan id was generated inside the engine**, from `Date.now()` plus six\\n//! random base-36 characters. Better than the clock alone, and still an I/O\\n//! decision made by a core that has neither a clock nor entropy. Host-supplied.\\n//!\\n//! # One thing recorded rather than changed\\n//!\\n//! `confidence` is set to 0.8, 0.6 or 0.7 by branch and **nothing reads it** — not\\n//! the approval decision, not the executor, not the log. It is preserved because a\\n//! host may well surface it, and named here as what it is: a number no code acts\\n//! on. If it is to gate anything, that is a decision to make deliberately rather\\n//! than by wiring up a value whose scale nobody has defined.\\n\\nuse std::collections::BTreeMap;\\n\\n/// How far a failure reaches.\\n///\\n/// Mirrors `mzizi-chaos`'s [`BlastRadius`](../mzizi_chaos/enum.BlastRadius.html)\\n/// including its `Unknown`, because the producer and the consumer of a diagnostic\\n/// have to share one vocabulary. Mirrored rather than imported: a registry\\n/// component must be installable on its own (CLAUDE.md §15.6).\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum BlastRadius {\\n /// Nothing else is down.\\n Isolated,\\n /// Some dependencies are down.\\n Partial,\\n /// Most or all are.\\n Systemic,\\n /// Nothing was probed, so nothing is known.\\n Unknown,\\n}\\n\\nimpl BlastRadius {\\n /// The wire spelling.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Isolated => \\\"isolated\\\",\\n Self::Partial => \\\"partial\\\",\\n Self::Systemic => \\\"systemic\\\",\\n Self::Unknown => \\\"unknown\\\",\\n }\\n }\\n}\\n\\n/// What kind of failure.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum ErrorType {\\n /// A component failed to render.\\n Render,\\n /// A request failed.\\n Network,\\n /// Data was missing or malformed.\\n Data,\\n /// An authentication or authorisation failure.\\n Auth,\\n /// A chain interaction failed.\\n Chain,\\n /// A cryptographic operation failed.\\n Crypto,\\n /// An operation exceeded its deadline.\\n Timeout,\\n}\\n\\nimpl ErrorType {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Render => \\\"render\\\",\\n Self::Network => \\\"network\\\",\\n Self::Data => \\\"data\\\",\\n Self::Auth => \\\"auth\\\",\\n Self::Chain => \\\"chain\\\",\\n Self::Crypto => \\\"crypto\\\",\\n Self::Timeout => \\\"timeout\\\",\\n }\\n }\\n\\n /// Whether fundi is forbidden from acting on this by itself.\\n ///\\n /// Auth, and only auth. The `.ts` states the rule — \\\"never auto-fix auth\\\" —\\n /// and then only reaches it on an isolated failure.\\n #[must_use]\\n pub const fn is_never_auto_fixed(self) -> bool {\\n matches!(self, Self::Auth)\\n }\\n}\\n\\n/// How severe an escalation is.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum EscalationSeverity {\\n /// Worth knowing.\\n Low,\\n /// Worth looking at.\\n Medium,\\n /// Worth waking somebody.\\n High,\\n /// Worth waking everybody.\\n Critical,\\n}\\n\\nimpl EscalationSeverity {\\n /// The wire spelling.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Low => \\\"low\\\",\\n Self::Medium => \\\"medium\\\",\\n Self::High => \\\"high\\\",\\n Self::Critical => \\\"critical\\\",\\n }\\n }\\n}\\n\\n/// How far a feature is degraded.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum DegradeLevel {\\n /// Reduced.\\n Partial,\\n /// Static content only.\\n Static,\\n /// Off.\\n Disabled,\\n}\\n\\nimpl DegradeLevel {\\n /// The wire spelling.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Partial => \\\"partial\\\",\\n Self::Static => \\\"static\\\",\\n Self::Disabled => \\\"disabled\\\",\\n }\\n }\\n}\\n\\n/// What a cache clear covers.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum CacheScope {\\n /// This client.\\n Local,\\n /// The edge.\\n Edge,\\n /// Everything.\\n All,\\n}\\n\\nimpl CacheScope {\\n /// The wire spelling.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Local => \\\"local\\\",\\n Self::Edge => \\\"edge\\\",\\n Self::All => \\\"all\\\",\\n }\\n }\\n}\\n\\n/// One thing to do about a failure.\\n#[derive(Debug, Clone, PartialEq)]\\npub enum RemediationAction {\\n /// Stop calling a service for a while.\\n CircuitBreak {\\n /// Which service.\\n service: String,\\n /// For how long.\\n duration_ms: u32,\\n },\\n /// Move traffic from one path to another.\\n FallbackSwitch {\\n /// The failing path.\\n from: String,\\n /// The path to use instead.\\n to: String,\\n },\\n /// Throw a cache away.\\n CacheClear {\\n /// How much of it.\\n scope: CacheScope,\\n },\\n /// Send work to a different node.\\n Reroute {\\n /// Where it was going.\\n from_node: String,\\n /// Where it goes now.\\n to_node: String,\\n },\\n /// Try again, more slowly each time.\\n RetryWithBackoff {\\n /// Which service.\\n service: String,\\n /// How many attempts.\\n max_retries: u32,\\n },\\n /// Turn a feature down.\\n DegradeFeature {\\n /// Which feature.\\n feature: String,\\n /// How far.\\n level: DegradeLevel,\\n },\\n /// Ask for more or less of something.\\n ScaleRequest {\\n /// Which way.\\n up: bool,\\n /// What.\\n resource: String,\\n },\\n /// Tell a person.\\n Escalate {\\n /// How loudly.\\n severity: EscalationSeverity,\\n /// What to say.\\n message: String,\\n },\\n}\\n\\nimpl RemediationAction {\\n /// The action's `type` tag, matching the `.ts` discriminant.\\n #[must_use]\\n pub const fn type_str(&self) -> &'static str {\\n match self {\\n Self::CircuitBreak { .. } => \\\"circuit-break\\\",\\n Self::FallbackSwitch { .. } => \\\"fallback-switch\\\",\\n Self::CacheClear { .. } => \\\"cache-clear\\\",\\n Self::Reroute { .. } => \\\"reroute\\\",\\n Self::RetryWithBackoff { .. } => \\\"retry-with-backoff\\\",\\n Self::DegradeFeature { .. } => \\\"degrade-feature\\\",\\n Self::ScaleRequest { .. } => \\\"scale-request\\\",\\n Self::Escalate { .. } => \\\"escalate\\\",\\n }\\n }\\n\\n /// Whether this action only informs, changing nothing about the system.\\n ///\\n /// Load-bearing for [`HealingPlan::may_execute`]: a plan awaiting approval may\\n /// still page somebody, because holding the page until a human notices defeats\\n /// the point of paging.\\n #[must_use]\\n pub const fn is_notification_only(&self) -> bool {\\n matches!(self, Self::Escalate { .. })\\n }\\n}\\n\\n/// How a probe answered.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum ProbeStatus {\\n /// Answered, and well.\\n Healthy,\\n /// Answered, badly.\\n Degraded,\\n /// Did not answer.\\n Error,\\n /// Did not answer in time.\\n Timeout,\\n}\\n\\nimpl ProbeStatus {\\n /// The wire spelling.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Healthy => \\\"healthy\\\",\\n Self::Degraded => \\\"degraded\\\",\\n Self::Error => \\\"error\\\",\\n Self::Timeout => \\\"timeout\\\",\\n }\\n }\\n}\\n\\n/// One dependency's answer.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct ProbeSummary {\\n /// What was probed.\\n pub target: String,\\n /// How it answered.\\n pub status: ProbeStatus,\\n}\\n\\n/// What N8 observed.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct DiagnosticInput {\\n /// How far it reaches.\\n pub blast_radius: BlastRadius,\\n /// Which component failed.\\n pub failed_component: String,\\n /// Which helix node it belongs to. Uncapped.\\n pub failed_node: u32,\\n /// What kind of failure.\\n pub error_type: ErrorType,\\n /// How many times.\\n pub error_count: u32,\\n /// How long since the first one, in milliseconds.\\n pub time_since_first_error_ms: f64,\\n /// What the dependencies said.\\n pub probe_results: Vec<ProbeSummary>,\\n}\\n\\n/// Whether a person has to look at this before it runs.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum Approval {\\n /// fundi may act.\\n NotRequired,\\n /// A person must clear it first.\\n ///\\n /// This is a STATE, not advice. The `.ts` makes it a boolean that\\n /// `autoHeal` — default true — overrides, so a plan declaring that it needs a\\n /// human executed anyway.\\n Required,\\n /// A person cleared it.\\n Granted,\\n}\\n\\n/// The number of errors, within the window, that trips a circuit break.\\npub const REPEAT_ERROR_THRESHOLD: u32 = 3;\\n/// The window that count is measured over, in milliseconds.\\npub const REPEAT_ERROR_WINDOW_MS: f64 = 60_000.0;\\n\\n/// What fundi decided to do.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct HealingPlan {\\n /// Supplied by the host, not derived from a clock. See defect 6.\\n pub id: String,\\n /// When, milliseconds since the Unix epoch.\\n pub at_ms: f64,\\n /// What it was reacting to.\\n pub diagnostic: DiagnosticInput,\\n /// What to do, in order.\\n pub actions: Vec<RemediationAction>,\\n /// A number nothing acts on. See the module docs.\\n pub confidence: f64,\\n /// Why, in words.\\n pub reasoning: String,\\n /// Whether a person has to clear it.\\n pub approval: Approval,\\n}\\n\\nimpl HealingPlan {\\n /// The actions a host may run right now.\\n ///\\n /// A plan awaiting approval yields only its notifications — escalating is how\\n /// the human who must approve it finds out there is something to approve, so\\n /// withholding that would deadlock the gate it enforces. Everything that\\n /// changes the system waits.\\n #[must_use]\\n pub fn may_execute(&self) -> Vec<&RemediationAction> {\\n match self.approval {\\n Approval::NotRequired | Approval::Granted => self.actions.iter().collect(),\\n Approval::Required => self\\n .actions\\n .iter()\\n .filter(|a| a.is_notification_only())\\n .collect(),\\n }\\n }\\n\\n /// Whether anything is being held back.\\n #[must_use]\\n pub fn is_held(&self) -> bool {\\n self.approval == Approval::Required\\n && self.actions.iter().any(|a| !a.is_notification_only())\\n }\\n\\n /// Record that a person cleared it.\\n pub fn grant_approval(&mut self) {\\n if self.approval == Approval::Required {\\n self.approval = Approval::Granted;\\n }\\n }\\n}\\n\\n/// Decide what to do about a failure.\\n///\\n/// The order is deliberate and differs from the `.ts`: the never-auto-fix rule is\\n/// consulted BEFORE the blast radius, because in the `.ts` the auth branch lives\\n/// inside the isolated-failure `else` and is therefore skipped for exactly the\\n/// partial and systemic auth failures it most needs to catch.\\n#[must_use]\\npub fn create_healing_plan(\\n id: impl Into<String>,\\n input: DiagnosticInput,\\n at_ms: f64,\\n) -> HealingPlan {\\n let mut actions = Vec::new();\\n let mut reasoning = String::new();\\n let mut confidence = 0.8;\\n let mut approval = Approval::NotRequired;\\n\\n // Rule 1: repeated failures trip the breaker, whatever else is true.\\n if input.error_count > REPEAT_ERROR_THRESHOLD\\n && input.time_since_first_error_ms < REPEAT_ERROR_WINDOW_MS\\n {\\n actions.push(RemediationAction::CircuitBreak {\\n service: input.failed_component.clone(),\\n duration_ms: 30_000,\\n });\\n reasoning.push_str(\\\"Repeated failures detected — circuit-breaking to prevent cascade. \\\");\\n }\\n\\n // Rule 2: auth is never fixed automatically, at ANY blast radius.\\n if input.error_type.is_never_auto_fixed() {\\n actions.push(RemediationAction::Escalate {\\n severity: match input.blast_radius {\\n BlastRadius::Systemic => EscalationSeverity::Critical,\\n _ => EscalationSeverity::High,\\n },\\n message: format!(\\n \\\"Auth failure in {} on N{} ({} blast radius)\\\",\\n input.failed_component,\\n input.failed_node,\\n input.blast_radius.as_str()\\n ),\\n });\\n reasoning.push_str(\\\"Auth error — escalating (never auto-fix auth). \\\");\\n return HealingPlan {\\n id: id.into(),\\n at_ms,\\n diagnostic: input,\\n actions,\\n confidence,\\n reasoning: reasoning.trim().to_owned(),\\n approval: Approval::Required,\\n };\\n }\\n\\n // Rule 3: chain and crypto fall back to the Web2 path, at any blast radius.\\n // The .ts reaches this only on an isolated failure.\\n if matches!(input.error_type, ErrorType::Chain | ErrorType::Crypto) {\\n actions.push(RemediationAction::FallbackSwitch {\\n from: \\\"web3\\\".to_owned(),\\n to: \\\"web2\\\".to_owned(),\\n });\\n actions.push(RemediationAction::DegradeFeature {\\n feature: input.failed_component.clone(),\\n level: DegradeLevel::Partial,\\n });\\n reasoning.push_str(\\\"Chain/crypto failure — falling back to Web2 path. \\\");\\n confidence = 0.7;\\n }\\n\\n match input.blast_radius {\\n BlastRadius::Systemic => {\\n actions.push(RemediationAction::DegradeFeature {\\n feature: \\\"non-critical\\\".to_owned(),\\n level: DegradeLevel::Disabled,\\n });\\n actions.push(RemediationAction::Escalate {\\n severity: EscalationSeverity::Critical,\\n message: format!(\\n \\\"Systemic failure: {} on N{}. {} errors in {}s.\\\",\\n input.failed_component,\\n input.failed_node,\\n input.error_count,\\n (input.time_since_first_error_ms / 1000.0).round()\\n ),\\n });\\n confidence = 0.6;\\n reasoning\\n .push_str(\\\"Systemic outage — disabling non-critical features, escalating to ops. \\\");\\n approval = Approval::Required;\\n }\\n BlastRadius::Partial => {\\n // Sorted, so two runs over the same probe set choose the same target.\\n // `healthyNodes[0]` in the .ts takes whichever probe came back first.\\n let mut healthy: Vec<&str> = input\\n .probe_results\\n .iter()\\n .filter(|p| p.status == ProbeStatus::Healthy)\\n .map(|p| p.target.as_str())\\n .collect();\\n healthy.sort_unstable();\\n if let Some(target) = healthy.first() {\\n actions.push(RemediationAction::Reroute {\\n from_node: input.failed_component.clone(),\\n to_node: (*target).to_owned(),\\n });\\n reasoning.push_str(&format!(\\n \\\"Partial failure — rerouting to healthy node: {target}. \\\"\\n ));\\n }\\n actions.push(RemediationAction::DegradeFeature {\\n feature: input.failed_component.clone(),\\n level: DegradeLevel::Partial,\\n });\\n reasoning.push_str(\\\"Degrading affected feature to partial mode. \\\");\\n }\\n BlastRadius::Unknown => {\\n // Nothing was probed. The .ts has no representation for this, so its\\n // `else` treats it as isolated — the calmest response, chosen at the\\n // moment the least is known.\\n actions.push(RemediationAction::Escalate {\\n severity: EscalationSeverity::Medium,\\n message: format!(\\n \\\"Blast radius unknown for {} on N{} — no dependencies were probed.\\\",\\n input.failed_component, input.failed_node\\n ),\\n });\\n confidence = 0.3;\\n reasoning.push_str(\\n \\\"Blast radius unknown — no dependency was probed, so no remediation is \\\\\\n chosen on the strength of it. \\\",\\n );\\n approval = Approval::Required;\\n }\\n BlastRadius::Isolated => match input.error_type {\\n ErrorType::Network | ErrorType::Timeout => {\\n actions.push(RemediationAction::RetryWithBackoff {\\n service: input.failed_component.clone(),\\n max_retries: 3,\\n });\\n actions.push(RemediationAction::FallbackSwitch {\\n from: \\\"cloud\\\".to_owned(),\\n to: \\\"edge\\\".to_owned(),\\n });\\n reasoning.push_str(\\n \\\"Isolated network issue — retrying with backoff, switching to edge. \\\",\\n );\\n }\\n ErrorType::Data | ErrorType::Render => {\\n actions.push(RemediationAction::CacheClear {\\n scope: CacheScope::Local,\\n });\\n actions.push(RemediationAction::RetryWithBackoff {\\n service: input.failed_component.clone(),\\n max_retries: 2,\\n });\\n reasoning\\n .push_str(\\\"Isolated data/render error — clearing local cache and retrying. \\\");\\n }\\n // Chain and crypto were handled above, at any radius; auth returned.\\n ErrorType::Chain | ErrorType::Crypto | ErrorType::Auth => {}\\n },\\n }\\n\\n HealingPlan {\\n id: id.into(),\\n at_ms,\\n diagnostic: input,\\n actions,\\n confidence,\\n reasoning: reasoning.trim().to_owned(),\\n approval,\\n }\\n}\\n\\n/// What happened when the host ran one action.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct ActionOutcome {\\n /// Which action.\\n pub action: RemediationAction,\\n /// Whether it worked.\\n pub success: bool,\\n /// What went wrong, when something did.\\n pub error: Option<String>,\\n}\\n\\n/// What happened to a whole plan.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct HealingResult {\\n /// Which plan.\\n pub plan_id: String,\\n /// How many actions succeeded.\\n pub actions_executed: usize,\\n /// How many did not.\\n pub actions_failed: usize,\\n /// How many were never attempted, because approval was pending.\\n ///\\n /// The `.ts` has no such count: an unapproved plan either ran in full or\\n /// returned `null`, so \\\"held\\\" and \\\"never happened\\\" were indistinguishable\\n /// afterwards.\\n pub actions_held: usize,\\n /// One entry per action attempted.\\n pub outcomes: Vec<ActionOutcome>,\\n /// When, milliseconds since the Unix epoch.\\n pub at_ms: f64,\\n}\\n\\n/// Fold what the host reported back into a result.\\n///\\n/// The core does not execute — clearing a cache, opening a circuit and paging\\n/// somebody are all I/O — so the host runs [`HealingPlan::may_execute`] and hands\\n/// the outcomes here.\\n#[must_use]\\npub fn plan_outcome(plan: &HealingPlan, outcomes: Vec<ActionOutcome>, at_ms: f64) -> HealingResult {\\n let executed = outcomes.iter().filter(|o| o.success).count();\\n let failed = outcomes.len() - executed;\\n HealingResult {\\n plan_id: plan.id.clone(),\\n actions_executed: executed,\\n actions_failed: failed,\\n actions_held: plan.actions.len().saturating_sub(outcomes.len()),\\n outcomes,\\n at_ms,\\n }\\n}\\n\\n/// How many of each action type a plan carries, for a summary line.\\n#[must_use]\\npub fn action_tally(plan: &HealingPlan) -> BTreeMap<&'static str, usize> {\\n let mut tally = BTreeMap::new();\\n for action in &plan.actions {\\n *tally.entry(action.type_str()).or_insert(0) += 1;\\n }\\n tally\\n}\\n\"}],\"contract\":null}",
|
|
1217
|
+
"mzizi-fundi-learning": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-fundi-learning\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"Fundi learning system. Tracks healing outcomes to improve future decisions. When an issue is resolved, records what failed, what Fundi suggested, what actually worked, and whether the same pattern recurs. Runs as part of the Fundi Cloudflare Worker — same Rust binary, different handler. Writes learned patterns to fundi_issues via Supabase REST API. Improves classification accuracy over time.\",\"crate\":{\"name\":\"mzizi-fundi\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n9-fundi/mzizi-fundi-learning.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N9 fundi — learning from healing outcomes.\\n//!\\n//! The Rust implementation of `mzizi-fundi-learning`: what fundi did, whether it\\n//! was right, and what that suggests about the next occurrence.\\n//!\\n//! # What this owns\\n//!\\n//! Aggregation and the severity suggestion. Not persistence — the `.ts` carries a\\n//! comment saying production stores outcomes in `fundi_healing_log` and computes\\n//! stats in SQL, which remains true and is the host's, because it is a database.\\n//!\\n//! # Two defects in the TypeScript, fixed here\\n//!\\n//! **1. `suggestSeverity` could LOWER the severity of a repeatedly recurring\\n//! defect.** The `.ts` returns `\\\"high\\\"` whenever something has recurred more than\\n//! twice — so a `critical` issue that keeps coming back is suggested as `high`,\\n//! which is exactly backwards. Recurrence is evidence that the first assessment\\n//! was too generous, never too harsh. [`suggest_severity`] escalates and never\\n//! de-escalates.\\n//!\\n//! **2. History grew without bound.** `outcomes.push` with no cap, in a component\\n//! whose whole purpose is to accumulate. The error tracker next door caps at 500;\\n//! this one did not, so a long-lived Worker or session leaks until it dies.\\n//!\\n//! # One thing preserved rather than \\\"fixed\\\"\\n//!\\n//! `accuracy` is `fundiWasCorrect / total`, and `total` includes outcomes fundi\\n//! never attempted — a human-fixed issue that fundi never claimed still drags the\\n//! denominator down. Arguably it should be measured over attempts. That is a\\n//! product decision about what the number means, not a porting one, and quietly\\n//! changing a published metric is how a dashboard starts lying in the flattering\\n//! direction. Preserved, with [`LearningStats::accuracy_over_attempts`] alongside\\n//! so the other reading is available without redefining the first.\\n\\nuse std::collections::BTreeMap;\\n\\n/// Who actually fixed it.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum ResolvedBy {\\n /// fundi's plan landed the fix.\\n Fundi,\\n /// A person fixed it.\\n Human,\\n /// Both contributed.\\n Both,\\n}\\n\\nimpl ResolvedBy {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Fundi => \\\"fundi\\\",\\n Self::Human => \\\"human\\\",\\n Self::Both => \\\"both\\\",\\n }\\n }\\n\\n /// Whether fundi attempted this at all.\\n #[must_use]\\n pub const fn fundi_attempted(self) -> bool {\\n matches!(self, Self::Fundi | Self::Both)\\n }\\n}\\n\\n/// Severity, ordered so escalation is a comparison rather than a lookup table.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum Severity {\\n /// Noise, or a well-handled edge.\\n Low,\\n /// Worth looking at.\\n Medium,\\n /// Degrades something users touch.\\n High,\\n /// A core guarantee broke.\\n Critical,\\n}\\n\\nimpl Severity {\\n /// The wire spelling.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Low => \\\"low\\\",\\n Self::Medium => \\\"medium\\\",\\n Self::High => \\\"high\\\",\\n Self::Critical => \\\"critical\\\",\\n }\\n }\\n\\n /// Parse a wire spelling, defaulting to [`Severity::Medium`].\\n ///\\n /// Medium rather than Low, because an unrecognised severity is an unknown\\n /// risk and treating the unknown as harmless is how things get missed.\\n #[must_use]\\n pub fn from_str_or_medium(value: &str) -> Self {\\n match value {\\n \\\"low\\\" => Self::Low,\\n \\\"high\\\" => Self::High,\\n \\\"critical\\\" => Self::Critical,\\n _ => Self::Medium,\\n }\\n }\\n}\\n\\n/// What happened to one issue.\\n#[derive(Debug, Clone)]\\npub struct HealingOutcome {\\n /// The issue number.\\n pub issue_id: u64,\\n /// Which component.\\n pub component: String,\\n /// Which helix node.\\n pub node: u32,\\n /// What kind of failure.\\n pub error_type: String,\\n /// How bad it was judged.\\n pub severity: Severity,\\n /// What fundi proposed.\\n pub plan_actions: Vec<String>,\\n /// What actually fixed it.\\n pub actual_fix: String,\\n /// Whether fundi's diagnosis was right.\\n pub fundi_was_correct: bool,\\n /// How long it took to resolve.\\n pub time_to_resolve_minutes: f64,\\n /// Whether it came back.\\n pub recurred: bool,\\n /// Who fixed it.\\n pub resolved_by: ResolvedBy,\\n /// When it was recorded, milliseconds since the Unix epoch.\\n pub recorded_at_ms: f64,\\n}\\n\\n/// A ranked count.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct Ranked {\\n /// The thing being counted.\\n pub name: String,\\n /// How many.\\n pub count: usize,\\n}\\n\\n/// What the history says.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct LearningStats {\\n /// How many outcomes are held.\\n pub total_issues: usize,\\n /// How many fundi resolved alone.\\n pub auto_fixed: usize,\\n /// How many it did not.\\n pub human_fixed: usize,\\n /// Correct diagnoses over ALL outcomes — the `.ts`'s definition.\\n pub accuracy: f64,\\n /// Correct diagnoses over the outcomes fundi actually attempted.\\n ///\\n /// The other reading of the same data, offered alongside rather than\\n /// replacing, so neither number has to be silently redefined.\\n pub accuracy_over_attempts: f64,\\n /// Mean minutes to resolve, rounded as the `.ts` does.\\n pub avg_time_to_resolve: f64,\\n /// The components failing most.\\n pub top_failing_components: Vec<Ranked>,\\n /// The error types occurring most.\\n pub top_error_types: Vec<Ranked>,\\n /// Fraction that came back.\\n pub recurrence_rate: f64,\\n}\\n\\nimpl LearningStats {\\n /// The empty case.\\n #[must_use]\\n pub fn empty() -> Self {\\n Self {\\n total_issues: 0,\\n auto_fixed: 0,\\n human_fixed: 0,\\n accuracy: 0.0,\\n accuracy_over_attempts: 0.0,\\n avg_time_to_resolve: 0.0,\\n top_failing_components: Vec::new(),\\n top_error_types: Vec::new(),\\n recurrence_rate: 0.0,\\n }\\n }\\n}\\n\\n/// Rank a tally, highest first, breaking ties by name.\\n///\\n/// The tie-break is not cosmetic. The `.ts` sorts a `Map` whose iteration order\\n/// is insertion order, so two components with equal counts swap places depending\\n/// on which failed first — and a \\\"top failing components\\\" list that reshuffles\\n/// between refreshes is one nobody trusts.\\nfn rank(counts: &BTreeMap<String, usize>, limit: usize) -> Vec<Ranked> {\\n let mut ranked: Vec<Ranked> = counts\\n .iter()\\n .map(|(name, &count)| Ranked {\\n name: name.clone(),\\n count,\\n })\\n .collect();\\n ranked.sort_by(|a, b| b.count.cmp(&a.count).then(a.name.cmp(&b.name)));\\n ranked.truncate(limit);\\n ranked\\n}\\n\\n/// The accumulated history.\\n#[derive(Debug, Clone)]\\npub struct LearningLog {\\n /// How many outcomes to retain. The `.ts` retains all of them, forever.\\n pub max_outcomes: usize,\\n outcomes: Vec<HealingOutcome>,\\n}\\n\\nimpl Default for LearningLog {\\n fn default() -> Self {\\n Self::new(1000)\\n }\\n}\\n\\nimpl LearningLog {\\n /// A log holding at most `max_outcomes`.\\n #[must_use]\\n pub fn new(max_outcomes: usize) -> Self {\\n Self {\\n max_outcomes,\\n outcomes: Vec::new(),\\n }\\n }\\n\\n /// Record an outcome, dropping the oldest if that exceeds the cap.\\n pub fn record(&mut self, outcome: HealingOutcome) {\\n self.outcomes.push(outcome);\\n if self.outcomes.len() > self.max_outcomes {\\n self.outcomes.remove(0);\\n }\\n }\\n\\n /// Every outcome held.\\n #[must_use]\\n pub fn outcomes(&self) -> &[HealingOutcome] {\\n &self.outcomes\\n }\\n\\n /// Everything recorded for one component.\\n #[must_use]\\n pub fn component_history(&self, component: &str) -> Vec<&HealingOutcome> {\\n self.outcomes\\n .iter()\\n .filter(|o| o.component == component)\\n .collect()\\n }\\n\\n /// Aggregate the history.\\n #[must_use]\\n pub fn stats(&self) -> LearningStats {\\n let total = self.outcomes.len();\\n if total == 0 {\\n return LearningStats::empty();\\n }\\n\\n let auto_fixed = self\\n .outcomes\\n .iter()\\n .filter(|o| o.resolved_by == ResolvedBy::Fundi)\\n .count();\\n let correct = self.outcomes.iter().filter(|o| o.fundi_was_correct).count();\\n let attempts = self\\n .outcomes\\n .iter()\\n .filter(|o| o.resolved_by.fundi_attempted())\\n .count();\\n let correct_attempts = self\\n .outcomes\\n .iter()\\n .filter(|o| o.fundi_was_correct && o.resolved_by.fundi_attempted())\\n .count();\\n let recurred = self.outcomes.iter().filter(|o| o.recurred).count();\\n let total_f = total as f64;\\n\\n let mut components: BTreeMap<String, usize> = BTreeMap::new();\\n let mut error_types: BTreeMap<String, usize> = BTreeMap::new();\\n for outcome in &self.outcomes {\\n *components.entry(outcome.component.clone()).or_insert(0) += 1;\\n *error_types.entry(outcome.error_type.clone()).or_insert(0) += 1;\\n }\\n\\n LearningStats {\\n total_issues: total,\\n auto_fixed,\\n human_fixed: total - auto_fixed,\\n accuracy: correct as f64 / total_f,\\n accuracy_over_attempts: if attempts == 0 {\\n 0.0\\n } else {\\n correct_attempts as f64 / attempts as f64\\n },\\n avg_time_to_resolve: (self\\n .outcomes\\n .iter()\\n .map(|o| o.time_to_resolve_minutes)\\n .sum::<f64>()\\n / total_f)\\n .round(),\\n top_failing_components: rank(&components, 10),\\n top_error_types: rank(&error_types, 5),\\n recurrence_rate: recurred as f64 / total_f,\\n }\\n }\\n\\n /// What severity the next occurrence of this defect probably deserves.\\n ///\\n /// Escalates and never de-escalates. The `.ts` returns `\\\"high\\\"` outright when\\n /// something has recurred more than twice, which *lowers* a `critical` defect\\n /// that keeps coming back — precisely inverted, since recurrence is evidence\\n /// the first assessment was too generous.\\n ///\\n /// With no history the answer is [`Severity::Medium`], as in the `.ts`.\\n #[must_use]\\n pub fn suggest_severity(&self, component: &str, error_type: &str) -> Severity {\\n let history: Vec<&HealingOutcome> = self\\n .outcomes\\n .iter()\\n .filter(|o| o.component == component && o.error_type == error_type)\\n .collect();\\n\\n let Some(last) = history.last() else {\\n return Severity::Medium;\\n };\\n\\n let recurrences = history.iter().filter(|o| o.recurred).count();\\n if recurrences > 2 {\\n // Escalate from where it already was, rather than to a fixed rung.\\n return last.severity.max(Severity::High);\\n }\\n last.severity\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1218
|
+
"mzizi-fundi-reporter": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-fundi-reporter\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"Bridge from N8 Assurance to N9 Fundi. When N8 components detect failures, the reporter pushes a structured signal to the Cloudflare Queue that triggers Fundi. Signal payload: component name, ecosystem node, error type, severity, diagnostic data. The Queue decouples N8 (detection) from N9 (healing) — N8 never waits for Fundi to respond. Also creates a fundi_issues row in Supabase as the append-only audit trail.\",\"crate\":{\"name\":\"mzizi-fundi\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n9-fundi/mzizi-fundi-reporter.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N9 fundi — turning an assurance signal into a filed defect.\\n//!\\n//! The Rust implementation of `mzizi-fundi-reporter`. This is the **healing**\\n//! exit from N8: a named defect a human can merge a fix for. The observation exit\\n//! is `mzizi-otel`, and they are not alternatives — see `docs/n8-telemetry.md`.\\n//!\\n//! # What this owns\\n//!\\n//! The cooldown decision, the labels, the issue title and body. Not the HTTP\\n//! POST: the host files the issue, as everywhere in the core.\\n//!\\n//! # Three defects in the TypeScript, fixed here\\n//!\\n//! **1. A failed report still started the cooldown.** The `.ts` records\\n//! `cooldowns.set(component, Date.now())` *before* the `fetch`, so a GitHub\\n//! outage — or a 401, or a rate limit — suppressed every retry for the next five\\n//! minutes. The signal was consumed without ever producing an issue. Here the\\n//! cooldown is recorded by [`CooldownLog::record_filed`], which a host calls only\\n//! after the file succeeds.\\n//!\\n//! **2. The cooldown key was the component alone.** A component with a render bug\\n//! and a network bug reported one and silently dropped the other for five\\n//! minutes, because they share a key. The key is component **and** error type, so\\n//! distinct defects on one component are distinct reports.\\n//!\\n//! **3. The issue body interpolated untrusted text into Markdown.** Every field\\n//! goes into a table cell, a code span or a link, and every field ultimately\\n//! comes from a runtime error message — which is attacker-influenced whenever any\\n//! user input reaches an exception. A backtick in `component` closes the code\\n//! span; a `|` in any value forges table columns; a newline plus `---` forges the\\n//! provenance footer this very file appends, so an injected report can claim to\\n//! have been filed by something it was not. [`escape_markdown_cell`] and\\n//! [`escape_code_span`] close that.\\n//!\\n//! GitHub sanitises rendered HTML, so this is content forgery rather than script\\n//! execution — but a fabricated \\\"Filed by\\\" line in an automated issue is exactly\\n//! the sort of thing a triager trusts without checking.\\n\\nuse std::collections::BTreeMap;\\n\\n/// How bad the reported failure is.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum ReportSeverity {\\n /// Noise, or a well-handled edge.\\n Low,\\n /// Worth looking at.\\n Medium,\\n /// Degrades something users touch.\\n High,\\n /// A core guarantee broke.\\n Critical,\\n}\\n\\nimpl ReportSeverity {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Low => \\\"low\\\",\\n Self::Medium => \\\"medium\\\",\\n Self::High => \\\"high\\\",\\n Self::Critical => \\\"critical\\\",\\n }\\n }\\n}\\n\\n/// What kind of failure this is.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum ErrorType {\\n /// A component failed to render.\\n Render,\\n /// A request failed.\\n Network,\\n /// Data was missing or malformed.\\n Data,\\n /// An authentication or authorisation failure.\\n Auth,\\n /// A chain interaction failed.\\n Chain,\\n /// A cryptographic operation failed.\\n Crypto,\\n /// An operation exceeded its deadline.\\n Timeout,\\n /// An accessibility conformance failure.\\n A11y,\\n /// A performance budget was exceeded.\\n Perf,\\n /// A conformity check failed.\\n Conformity,\\n /// A service-level objective was breached.\\n Slo,\\n}\\n\\nimpl ErrorType {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Render => \\\"render\\\",\\n Self::Network => \\\"network\\\",\\n Self::Data => \\\"data\\\",\\n Self::Auth => \\\"auth\\\",\\n Self::Chain => \\\"chain\\\",\\n Self::Crypto => \\\"crypto\\\",\\n Self::Timeout => \\\"timeout\\\",\\n Self::A11y => \\\"a11y\\\",\\n Self::Perf => \\\"perf\\\",\\n Self::Conformity => \\\"conformity\\\",\\n Self::Slo => \\\"slo\\\",\\n }\\n }\\n}\\n\\n/// A failure worth filing.\\n#[derive(Debug, Clone)]\\npub struct FundiReport {\\n /// Which component failed.\\n pub component: String,\\n /// Which helix node it belongs to.\\n pub node: u32,\\n /// How bad.\\n pub severity: ReportSeverity,\\n /// What kind.\\n pub error_type: ErrorType,\\n /// Which N8 component observed it.\\n pub source: String,\\n /// One-line summary.\\n pub title: String,\\n /// The detail.\\n pub description: String,\\n /// Where to see the component.\\n pub portal_url: Option<String>,\\n /// Structured diagnostic, already serialised by the host.\\n pub diagnostic: Option<String>,\\n /// Mini-apps implicated.\\n pub affected_mini_apps: Vec<String>,\\n /// Components that may be affected.\\n pub blast_radius: Vec<String>,\\n}\\n\\nimpl FundiReport {\\n /// The cooldown key: component AND error type.\\n ///\\n /// Keying on the component alone — which the `.ts` does — means a render bug\\n /// and a network bug on one component share a bucket, so the second is\\n /// silently dropped for the cooldown window.\\n #[must_use]\\n pub fn cooldown_key(&self) -> String {\\n format!(\\\"{}:{}\\\", self.component, self.error_type.as_str())\\n }\\n}\\n\\n/// Escape a value destined for a Markdown table cell.\\n///\\n/// A `|` forges columns, a newline escapes the row entirely, and a leading `-`\\n/// on its own line can start a list or a rule. Backslashes go first, or the\\n/// escapes this adds would themselves be escapable.\\n#[must_use]\\npub fn escape_markdown_cell(value: &str) -> String {\\n let mut out = String::with_capacity(value.len());\\n for c in value.chars() {\\n match c {\\n '\\\\\\\\' => out.push_str(\\\"\\\\\\\\\\\\\\\\\\\"),\\n '|' => out.push_str(\\\"\\\\\\\\|\\\"),\\n // A cell cannot contain a line break; a space preserves readability\\n // where a dropped character would silently join two words.\\n '\\\\n' | '\\\\r' => out.push(' '),\\n c => out.push(c),\\n }\\n }\\n out\\n}\\n\\n/// Escape a value destined for a backtick code span.\\n///\\n/// A backtick closes the span and lets everything after it render as Markdown,\\n/// which is how a component name becomes a heading. Replaced rather than\\n/// backslash-escaped, because a backslash does not escape a backtick inside a\\n/// code span — only widening the fence does, and that cannot be done per value.\\n#[must_use]\\npub fn escape_code_span(value: &str) -> String {\\n value.replace('`', \\\"'\\\").replace(['\\\\n', '\\\\r'], \\\" \\\")\\n}\\n\\n/// The labels an issue carries.\\n#[must_use]\\npub fn labels_for(report: &FundiReport) -> Vec<String> {\\n vec![\\n format!(\\\"fundi:severity/{}\\\", report.severity.as_str()),\\n format!(\\\"fundi:node/{}\\\", report.node),\\n format!(\\\"fundi:type/{}\\\", report.error_type.as_str()),\\n format!(\\\"fundi:source/{}\\\", escape_code_span(&report.source)),\\n ]\\n}\\n\\n/// The issue title.\\n#[must_use]\\npub fn issue_title(report: &FundiReport) -> String {\\n format!(\\n \\\"[{}] {}\\\",\\n escape_code_span(&report.component),\\n report.title.replace(['\\\\n', '\\\\r'], \\\" \\\")\\n )\\n}\\n\\n/// The issue body, in Markdown.\\n#[must_use]\\npub fn issue_body(report: &FundiReport) -> String {\\n let mut b = String::from(\\\"## Component Failure Report\\\\n\\\\n| Field | Value |\\\\n|---|---|\\\\n\\\");\\n b.push_str(&format!(\\n \\\"| Component | `{}` |\\\\n\\\",\\n escape_code_span(&report.component)\\n ));\\n b.push_str(&format!(\\\"| Node | {} |\\\\n\\\", report.node));\\n b.push_str(&format!(\\\"| Severity | {} |\\\\n\\\", report.severity.as_str()));\\n b.push_str(&format!(\\n \\\"| Error Type | {} |\\\\n\\\",\\n report.error_type.as_str()\\n ));\\n b.push_str(&format!(\\n \\\"| Source | {} |\\\\n\\\",\\n escape_markdown_cell(&report.source)\\n ));\\n if let Some(url) = &report.portal_url {\\n // Angle brackets are Markdown's own literal-URL form, so a `)` in the\\n // URL cannot terminate the link early.\\n b.push_str(&format!(\\n \\\"| Portal | [View](<{}>) |\\\\n\\\",\\n url.replace(['<', '>', '\\\\n', '\\\\r'], \\\"\\\")\\n ));\\n }\\n b.push_str(&format!(\\\"\\\\n### Description\\\\n\\\\n{}\\\\n\\\", report.description));\\n if !report.affected_mini_apps.is_empty() {\\n b.push_str(&format!(\\n \\\"\\\\n### Affected Mini-Apps\\\\n\\\\n{}\\\\n\\\",\\n report\\n .affected_mini_apps\\n .iter()\\n .map(|m| escape_markdown_cell(m))\\n .collect::<Vec<_>>()\\n .join(\\\", \\\")\\n ));\\n }\\n if !report.blast_radius.is_empty() {\\n b.push_str(&format!(\\n \\\"\\\\n### Blast Radius\\\\n\\\\n{}\\\\n\\\",\\n report\\n .blast_radius\\n .iter()\\n .map(|c| format!(\\\"`{}`\\\", escape_code_span(c)))\\n .collect::<Vec<_>>()\\n .join(\\\", \\\")\\n ));\\n }\\n if let Some(diagnostic) = &report.diagnostic {\\n // A four-backtick fence, so a three-backtick fence inside the diagnostic\\n // cannot close it and spill the rest into prose.\\n b.push_str(&format!(\\n \\\"\\\\n### Diagnostic\\\\n\\\\n````json\\\\n{}\\\\n````\\\\n\\\",\\n diagnostic.replace(\\\"````\\\", \\\"'''\\\")\\n ));\\n }\\n b.push_str(\\\"\\\\n---\\\\n*Filed by mzizi-fundi-reporter (the N8 assurance to N9 fundi bridge)*\\\\n\\\");\\n b\\n}\\n\\n/// Why a report was not filed.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum NotFiled {\\n /// An identical defect was filed recently.\\n InCooldown,\\n}\\n\\n/// Per-defect cooldowns.\\n///\\n/// The host records a filing only once it has actually succeeded, which is the\\n/// fix for the `.ts` consuming a signal it never delivered.\\n#[derive(Debug, Clone, Default)]\\npub struct CooldownLog {\\n /// Seconds a defect stays suppressed after a successful filing.\\n pub cooldown_seconds: f64,\\n filed_at_ms: BTreeMap<String, f64>,\\n}\\n\\nimpl CooldownLog {\\n /// A log with the `.ts`'s default of five minutes.\\n #[must_use]\\n pub fn new(cooldown_seconds: f64) -> Self {\\n Self {\\n cooldown_seconds,\\n filed_at_ms: BTreeMap::new(),\\n }\\n }\\n\\n /// Whether this report may be filed now.\\n ///\\n /// # Errors\\n ///\\n /// Returns [`NotFiled::InCooldown`] when an identical defect was filed inside\\n /// the window.\\n pub fn may_file(&self, report: &FundiReport, now_ms: f64) -> Result<(), NotFiled> {\\n match self.filed_at_ms.get(&report.cooldown_key()) {\\n Some(last) if now_ms - last < self.cooldown_seconds * 1000.0 => {\\n Err(NotFiled::InCooldown)\\n }\\n _ => Ok(()),\\n }\\n }\\n\\n /// Record that this report WAS filed. Call only on success.\\n pub fn record_filed(&mut self, report: &FundiReport, now_ms: f64) {\\n self.filed_at_ms.insert(report.cooldown_key(), now_ms);\\n }\\n\\n /// How many distinct defects are suppressed.\\n #[must_use]\\n pub fn len(&self) -> usize {\\n self.filed_at_ms.len()\\n }\\n\\n /// Whether anything is suppressed.\\n #[must_use]\\n pub fn is_empty(&self) -> bool {\\n self.filed_at_ms.is_empty()\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1219
|
+
"mzizi-gauge-card": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-gauge-card\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Branded metric card with radial arc gauge, value display, label, and contextual description. The universal pattern extracted from mukoko-weather MetricCard. Used for UV index, trust scores, completion percentages, health vitals, battery levels, signal strength — any bounded numeric value displayed as a gauge. Composes the arc-gauge primitive with brand styling.\",\"crate\":{\"name\":\"mzizi-brand\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n3-brand/mzizi-gauge-card.rs\",\"type\":\"registry:rust\",\"content\":\"//! MZIZI GAUGE CARD — N3 brand, Dioxus. A Mzizi Roots component.\\n//!\\n//! The Rust sibling of `mzizi-gauge-card.tsx`: a 270° radial arc gauge with a value, a label\\n//! and a line of context — the pattern from mukoko weather's metric cards. Same `data-slot`,\\n//! same `role=\\\"meter\\\"` with its value range, same five mineral strokes, same loading skeleton.\\n//!\\n//! # The arc is computed, and clamped, in one place\\n//!\\n//! [`arc_dash`] is the `.tsx`'s `filled = clamp(percent) / 100 * ARC_LEN`. It also clamps\\n//! `aria-valuenow`, which the `.tsx` rounds without clamping, so a `percent` of 140 announces\\n//! 140 against an `aria-valuemax` of 100. **The `.tsx` sibling still announces the\\n//! unclamped value.**\\n//!\\n//! # The harness's motion preference is a prop\\n//!\\n//! [`MziziGaugeCardProps::prefers_reduced_motion`] replaces `useMziziHarness`'s\\n//! `motion.prefersReduced`, as `mzizi-update-prompt.rs` does in N7.\\n//!\\n//! # The portal link is on both branches\\n//!\\n//! The `.tsx` sets `data-portal` on the loading skeleton only. This port sets it on the loaded\\n//! view too, so the link from rendered markup back to the registry page does not depend on\\n//! whether the data has arrived.\\n\\nuse dioxus::prelude::*;\\n\\n/// The component's contract, in the clause grammar of the Mzizi language's `contract … end`\\n/// block (mzizi-dev/mzizi `design/RFC-0006-contracts.md`). `mzizi-brand`'s contract suite\\n/// evaluates every clause against this component's server-rendered markup and fails on any\\n/// clause it cannot evaluate.\\npub const CONTRACT: &str = r#\\\"contract\\n slot is \\\"mzizi-gauge-card\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-gauge-card\\\"\\n role is \\\"article\\\"\\n mineral is \\\"cobalt\\\"\\n every gauge_mineral stroke uses \\\"--color-\\\"\\nend\\\"#;\\n\\n/// Arc radius in the 64-unit viewBox.\\npub const ARC_R: f64 = 26.0;\\n/// Circumference of the arc's circle.\\npub const ARC_C: f64 = 2.0 * std::f64::consts::PI * ARC_R;\\n/// The visible arc: three quarters of the circle.\\npub const ARC_LEN: f64 = ARC_C * 0.75;\\n\\n/// The stroke mineral. Matches `mineralStrokes` in the `.tsx`.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]\\npub enum GaugeMineral {\\n /// Cobalt, the default.\\n #[default]\\n Cobalt,\\n /// Tanzanite.\\n Tanzanite,\\n /// Malachite.\\n Malachite,\\n /// Gold.\\n Gold,\\n /// Terracotta.\\n Terracotta,\\n}\\n\\nimpl GaugeMineral {\\n /// Every mineral, for exhaustive checks.\\n pub const ALL: [Self; 5] = [\\n Self::Cobalt,\\n Self::Tanzanite,\\n Self::Malachite,\\n Self::Gold,\\n Self::Terracotta,\\n ];\\n\\n /// The stroke colour.\\n #[must_use]\\n pub const fn stroke(self) -> &'static str {\\n match self {\\n Self::Cobalt => \\\"var(--color-cobalt, #00B0FF)\\\",\\n Self::Tanzanite => \\\"var(--color-tanzanite, #B388FF)\\\",\\n Self::Malachite => \\\"var(--color-malachite, #64FFDA)\\\",\\n Self::Gold => \\\"var(--color-gold, #FFD740)\\\",\\n Self::Terracotta => \\\"var(--color-terracotta, #D4A574)\\\",\\n }\\n }\\n\\n /// The value the `.tsx` accepts for `mineral`.\\n #[must_use]\\n pub const fn slug(self) -> &'static str {\\n match self {\\n Self::Cobalt => \\\"cobalt\\\",\\n Self::Tanzanite => \\\"tanzanite\\\",\\n Self::Malachite => \\\"malachite\\\",\\n Self::Gold => \\\"gold\\\",\\n Self::Terracotta => \\\"terracotta\\\",\\n }\\n }\\n}\\n\\n/// `percent` clamped to 0–100; NaN reads as 0.\\n#[must_use]\\npub fn clamp_percent(percent: f64) -> f64 {\\n if percent.is_nan() {\\n 0.0\\n } else {\\n percent.clamp(0.0, 100.0)\\n }\\n}\\n\\n/// The filled arc length for `percent`, as a `stroke-dasharray` value.\\n#[must_use]\\npub fn arc_dash(percent: f64) -> String {\\n let filled = clamp_percent(percent) / 100.0 * ARC_LEN;\\n format!(\\\"{filled} {ARC_C}\\\")\\n}\\n\\n/// The entrance animation, empty when motion is reduced — the `.tsx`'s `animStyle`, with the\\n/// harness's 200ms decelerate curve.\\n#[must_use]\\npub fn entrance_style(prefers_reduced_motion: bool) -> &'static str {\\n if prefers_reduced_motion {\\n \\\"\\\"\\n } else {\\n \\\"animation: nyuchi-fade-slide-up 200ms cubic-bezier(0, 0, 0.2, 1) both;\\\"\\n }\\n}\\n\\n/// Props for [`MziziGaugeCard`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziGaugeCardProps {\\n /// Renders the skeleton instead of the gauge.\\n #[props(default)]\\n pub loading: bool,\\n /// Icon before the label.\\n #[props(default)]\\n pub icon: Option<Element>,\\n /// What is measured, e.g. \\\"UV index\\\".\\n #[props(into)]\\n pub label: String,\\n /// The value in the middle of the arc, e.g. \\\"7\\\".\\n #[props(into)]\\n pub value: String,\\n /// How full the arc is, 0–100.\\n pub percent: f64,\\n /// Line of context under the label, e.g. \\\"High\\\".\\n #[props(default)]\\n pub context: Option<String>,\\n /// Classes for the context line; defaults to muted.\\n #[props(default)]\\n pub context_class: Option<String>,\\n /// A stroke colour that overrides `mineral`.\\n #[props(default)]\\n pub stroke_color: Option<String>,\\n /// The stroke mineral.\\n #[props(default)]\\n pub mineral: GaugeMineral,\\n /// Replaces the harness's `motion.prefersReduced`.\\n #[props(default)]\\n pub prefers_reduced_motion: bool,\\n /// Extra classes, appended last.\\n #[props(default)]\\n pub class: String,\\n}\\n\\n/// A radial arc gauge card.\\n#[component]\\npub fn MziziGaugeCard(props: MziziGaugeCardProps) -> Element {\\n if props.loading {\\n return rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-gauge-card\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-gauge-card\\\",\\n \\\"data-loading\\\": \\\"\\\",\\n role: \\\"article\\\",\\n class: \\\"flex animate-pulse flex-col items-center gap-3 rounded-[var(--radius-lg,14px)] bg-card p-4 ring-1 ring-foreground/10\\\",\\n div { class: \\\"size-16 rounded-full bg-muted\\\" }\\n div { class: \\\"h-3 w-16 rounded bg-muted\\\" }\\n div { class: \\\"h-2.5 w-24 rounded bg-muted\\\" }\\n }\\n };\\n }\\n let color = props\\n .stroke_color\\n .clone()\\n .unwrap_or_else(|| props.mineral.stroke().to_owned());\\n let now = clamp_percent(props.percent).round();\\n let base = \\\"flex flex-col items-center gap-2 rounded-[var(--radius-lg,14px)] bg-card p-4 text-center ring-1 ring-foreground/10 transition-shadow hover:shadow-md focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary,#00B0FF)]\\\";\\n let root = if props.class.is_empty() {\\n base.to_owned()\\n } else {\\n format!(\\\"{base} {}\\\", props.class)\\n };\\n let context_class = props\\n .context_class\\n .clone()\\n .unwrap_or_else(|| \\\"text-muted-foreground\\\".to_owned());\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-gauge-card\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-gauge-card\\\",\\n \\\"data-mineral\\\": props.mineral.slug(),\\n role: \\\"article\\\",\\n style: entrance_style(props.prefers_reduced_motion),\\n class: \\\"{root}\\\",\\n div {\\n class: \\\"relative flex shrink-0 items-center justify-center\\\",\\n role: \\\"meter\\\",\\n \\\"aria-valuenow\\\": \\\"{now}\\\",\\n \\\"aria-valuemin\\\": \\\"0\\\",\\n \\\"aria-valuemax\\\": \\\"100\\\",\\n \\\"aria-label\\\": \\\"{props.value}\\\",\\n svg { width: \\\"72\\\", height: \\\"72\\\", view_box: \\\"0 0 64 64\\\", class: \\\"overflow-visible\\\", \\\"aria-hidden\\\": \\\"true\\\",\\n circle {\\n cx: \\\"32\\\",\\n cy: \\\"32\\\",\\n r: \\\"{ARC_R}\\\",\\n fill: \\\"none\\\",\\n class: \\\"stroke-muted-foreground/15\\\",\\n stroke_width: \\\"5\\\",\\n stroke_linecap: \\\"round\\\",\\n stroke_dasharray: \\\"{ARC_LEN} {ARC_C}\\\",\\n transform: \\\"rotate(135 32 32)\\\",\\n }\\n circle {\\n cx: \\\"32\\\",\\n cy: \\\"32\\\",\\n r: \\\"{ARC_R}\\\",\\n fill: \\\"none\\\",\\n style: \\\"stroke: {color};\\\",\\n class: \\\"transition-all duration-500\\\",\\n stroke_width: \\\"5\\\",\\n stroke_linecap: \\\"round\\\",\\n stroke_dasharray: arc_dash(props.percent),\\n transform: \\\"rotate(135 32 32)\\\",\\n }\\n }\\n span { class: \\\"absolute inset-0 flex items-center justify-center text-base font-bold text-foreground\\\",\\n \\\"{props.value}\\\"\\n }\\n }\\n div { class: \\\"min-w-0\\\",\\n div { class: \\\"flex items-center justify-center gap-1.5\\\",\\n if let Some(icon) = &props.icon {\\n span { class: \\\"text-muted-foreground\\\", \\\"aria-hidden\\\": \\\"true\\\", {icon} }\\n }\\n p { class: \\\"text-sm font-medium text-muted-foreground\\\", \\\"{props.label}\\\" }\\n }\\n if let Some(context) = &props.context {\\n p { class: \\\"mt-1 text-sm {context_class}\\\", \\\"{context}\\\" }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn the_arc_is_three_quarters_of_the_circle() {\\n assert!((ARC_LEN / ARC_C - 0.75).abs() < f64::EPSILON);\\n }\\n\\n #[test]\\n fn percent_is_clamped_both_ways() {\\n assert!((clamp_percent(140.0) - 100.0).abs() < f64::EPSILON);\\n assert!(clamp_percent(-5.0).abs() < f64::EPSILON);\\n assert!(clamp_percent(f64::NAN).abs() < f64::EPSILON);\\n }\\n\\n #[test]\\n fn a_full_gauge_fills_the_visible_arc() {\\n assert_eq!(arc_dash(100.0), format!(\\\"{ARC_LEN} {ARC_C}\\\"));\\n assert_eq!(arc_dash(0.0), format!(\\\"0 {ARC_C}\\\"));\\n }\\n}\\n\"}],\"contract\":\"contract\\n slot is \\\"mzizi-gauge-card\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-gauge-card\\\"\\n role is \\\"article\\\"\\n mineral is \\\"cobalt\\\"\\n every gauge_mineral stroke uses \\\"--color-\\\"\\nend\"}",
|
|
1220
|
+
"mzizi-hero-stat": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-hero-stat\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Branded large primary value display with condition label, secondary stats, icon, and share button. The universal pattern extracted from mukoko-weather CurrentConditions. Used for: temperature (Weather), portfolio value (Wallet), trust score (ID), daily steps (Health), total revenue (BushTrade). Any screen that needs a single large number as its hero.\",\"crate\":{\"name\":\"mzizi-brand\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n3-brand/mzizi-hero-stat.rs\",\"type\":\"registry:rust\",\"content\":\"//! MZIZI HERO STAT — N3 brand, Dioxus. A Mzizi Roots component.\\n//!\\n//! The Rust sibling of `mzizi-hero-stat.tsx`: one large primary value with a unit, a\\n//! condition, a subtitle and a row of secondary stats — the pattern from mukoko weather's\\n//! current conditions. Same `data-slot`, same `role=\\\"region\\\"`, same loading skeleton.\\n//!\\n//! # The harness's motion preference is a prop\\n//!\\n//! The `.tsx` reads `motion.prefersReduced` from `useMziziHarness`, which has no Rust port.\\n//! [`MziziHeroStatProps::prefers_reduced_motion`] takes the one bit the component branches on,\\n//! the same move `mzizi-update-prompt.rs` makes in N7.\\n//!\\n//! # The icon is an element\\n//!\\n//! `icon` is already a `ReactNode` in the `.tsx`, so it maps straight to an [`Element`].\\n//!\\n//! # The skeleton keeps the region's name\\n//!\\n//! The `.tsx`'s loading branch is a `role=\\\"region\\\"` with no accessible name, so a screen\\n//! reader announces an anonymous region. This port labels it with `title` in both branches.\\n//! **The `.tsx` sibling still has the unnamed skeleton.**\\n\\nuse dioxus::prelude::*;\\n\\n/// The component's contract, in the clause grammar of the Mzizi language's `contract … end`\\n/// block (mzizi-dev/mzizi `design/RFC-0006-contracts.md`). `mzizi-brand`'s contract suite\\n/// evaluates every clause against this component's server-rendered markup and fails on any\\n/// clause it cannot evaluate.\\npub const CONTRACT: &str = r#\\\"contract\\n slot is \\\"mzizi-hero-stat\\\"\\n role is \\\"region\\\"\\n label not_empty\\n button \\\"Share\\\" min_height 48\\nend\\\"#;\\n\\n/// The entrance animation, empty when motion is reduced — the `.tsx`'s `animStyle`, with the\\n/// harness's 200ms decelerate curve.\\n#[must_use]\\npub fn entrance_style(prefers_reduced_motion: bool) -> &'static str {\\n if prefers_reduced_motion {\\n \\\"\\\"\\n } else {\\n \\\"animation: nyuchi-fade-slide-up 200ms cubic-bezier(0, 0, 0.2, 1) both;\\\"\\n }\\n}\\n\\n/// The accessible name of the value, matching the `.tsx`'s `` `${value}${unit || \\\"\\\"}` ``.\\n#[must_use]\\npub fn value_label(value: &str, unit: Option<&str>) -> String {\\n format!(\\\"{value}{}\\\", unit.unwrap_or(\\\"\\\"))\\n}\\n\\n/// One secondary stat, e.g. \\\"Humidity 62%\\\".\\n#[derive(Clone, Debug, PartialEq)]\\npub struct SecondaryStat {\\n /// Label, shown muted.\\n pub label: String,\\n /// Value, shown strong.\\n pub value: String,\\n}\\n\\n/// Join a base class string with a consumer's extra classes, without a trailing space.\\nfn join(base: &str, extra: &str) -> String {\\n if extra.is_empty() {\\n base.to_owned()\\n } else {\\n format!(\\\"{base} {extra}\\\")\\n }\\n}\\n\\nconst SHARE: &str = \\\"flex min-h-[48px] min-w-[48px] items-center justify-center rounded-[var(--radius-md,12px)] bg-muted px-3 text-sm text-muted-foreground transition-colors hover:bg-muted/80 hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary,#00B0FF)]\\\";\\n\\n/// Props for [`MziziHeroStat`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziHeroStatProps {\\n /// Renders the skeleton instead of the value.\\n #[props(default)]\\n pub loading: bool,\\n /// What the value is, e.g. \\\"Harare now\\\". Also the region's accessible name.\\n #[props(into)]\\n pub title: String,\\n /// The primary value.\\n #[props(into)]\\n pub value: String,\\n /// Unit after the value.\\n #[props(default)]\\n pub unit: Option<String>,\\n /// Condition line, in the serif face.\\n #[props(default)]\\n pub condition: Option<String>,\\n /// Muted subtitle.\\n #[props(default)]\\n pub subtitle: Option<String>,\\n /// Secondary stats.\\n #[props(default)]\\n pub secondary_stats: Vec<SecondaryStat>,\\n /// Icon, tinted with the brand accent.\\n #[props(default)]\\n pub icon: Option<Element>,\\n /// Shows a Share button when set.\\n #[props(default)]\\n pub on_share: Option<EventHandler<()>>,\\n /// Replaces the harness's `motion.prefersReduced`.\\n #[props(default)]\\n pub prefers_reduced_motion: bool,\\n /// Extra classes, appended last.\\n #[props(default)]\\n pub class: String,\\n}\\n\\n/// A large primary value with context and secondary stats.\\n#[component]\\npub fn MziziHeroStat(props: MziziHeroStatProps) -> Element {\\n if props.loading {\\n return rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-hero-stat\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-hero-stat\\\",\\n \\\"data-loading\\\": \\\"\\\",\\n role: \\\"region\\\",\\n \\\"aria-label\\\": \\\"{props.title}\\\",\\n class: \\\"animate-pulse space-y-3 rounded-[var(--radius-lg,14px)] bg-card p-5 ring-1 ring-foreground/10\\\",\\n div { class: \\\"h-3 w-24 rounded bg-muted\\\" }\\n div { class: \\\"h-16 w-32 rounded bg-muted\\\" }\\n div { class: \\\"h-4 w-20 rounded bg-muted\\\" }\\n div { class: \\\"h-3 w-40 rounded bg-muted\\\" }\\n }\\n };\\n }\\n let label = value_label(&props.value, props.unit.as_deref());\\n let share_label = format!(\\\"Share {}\\\", props.title);\\n rsx! {\\n section {\\n \\\"data-slot\\\": \\\"mzizi-hero-stat\\\",\\n role: \\\"region\\\",\\n \\\"aria-label\\\": \\\"{props.title}\\\",\\n style: entrance_style(props.prefers_reduced_motion),\\n class: join(\\\"rounded-[var(--radius-lg,14px)] bg-card p-5 ring-1 ring-foreground/10 sm:p-6\\\", &props.class),\\n div { class: \\\"flex items-start justify-between gap-2\\\",\\n div { class: \\\"min-w-0\\\",\\n p { class: \\\"text-sm font-medium text-muted-foreground\\\", \\\"{props.title}\\\" }\\n div { class: \\\"mt-1 flex items-baseline gap-1\\\",\\n span {\\n class: \\\"font-mono text-6xl font-bold tracking-tighter text-foreground sm:text-7xl\\\",\\n \\\"aria-label\\\": \\\"{label}\\\",\\n \\\"{props.value}\\\"\\n }\\n if let Some(unit) = &props.unit {\\n span { class: \\\"text-2xl font-light text-muted-foreground\\\", \\\"aria-hidden\\\": \\\"true\\\", \\\"{unit}\\\" }\\n }\\n }\\n if let Some(condition) = &props.condition {\\n p { class: \\\"mt-1 text-base font-semibold text-foreground\\\", style: \\\"font-family: var(--font-serif);\\\",\\n \\\"{condition}\\\"\\n }\\n }\\n if let Some(subtitle) = &props.subtitle {\\n p { class: \\\"mt-1 text-sm text-muted-foreground\\\", \\\"{subtitle}\\\" }\\n }\\n if !props.secondary_stats.is_empty() {\\n div { class: \\\"mt-2 flex flex-wrap gap-x-3 gap-y-1 text-sm text-muted-foreground\\\",\\n for (i, s) in props.secondary_stats.iter().enumerate() {\\n span { key: \\\"{i}\\\",\\n \\\"{s.label} \\\"\\n strong { class: \\\"text-foreground\\\", \\\"{s.value}\\\" }\\n }\\n }\\n }\\n }\\n }\\n div { class: \\\"flex shrink-0 flex-col items-end gap-2\\\",\\n if let Some(icon) = &props.icon {\\n div { class: \\\"text-[var(--brand-accent,var(--color-cobalt,#00B0FF))]\\\", {icon} }\\n }\\n if let Some(on_share) = props.on_share {\\n button {\\n r#type: \\\"button\\\",\\n \\\"aria-label\\\": \\\"{share_label}\\\",\\n class: SHARE,\\n onclick: move |_| on_share.call(()),\\n \\\"Share\\\"\\n }\\n }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn value_label_appends_the_unit() {\\n assert_eq!(value_label(\\\"24\\\", Some(\\\"°C\\\")), \\\"24°C\\\");\\n assert_eq!(value_label(\\\"24\\\", None), \\\"24\\\");\\n }\\n\\n #[test]\\n fn reduced_motion_drops_the_animation() {\\n assert_eq!(entrance_style(true), \\\"\\\");\\n assert!(entrance_style(false).contains(\\\"nyuchi-fade-slide-up\\\"));\\n }\\n}\\n\"}],\"contract\":\"contract\\n slot is \\\"mzizi-hero-stat\\\"\\n role is \\\"region\\\"\\n label not_empty\\n button \\\"Share\\\" min_height 48\\nend\"}",
|
|
1221
|
+
"mzizi-incident-manager": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-incident-manager\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"Incident lifecycle management: detect → triage → mitigate → resolve → postmortem. Tracks incidents with severity, affected components (via backlinks), timeline of actions, and generates postmortem templates. Competitors: PagerDuty, incident.io.\",\"crate\":{\"name\":\"mzizi-assurance\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n8-assurance/mzizi-incident-manager.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N8 assurance — the incident lifecycle, from detection to postmortem.\\n//!\\n//! The Rust implementation of `mzizi-incident-manager`. The state machine, the\\n//! timings, and the postmortem document; not the storage, the clock, or the\\n//! delivery.\\n//!\\n//! # Six defects in the TypeScript, fixed here\\n//!\\n//! **1. The incident id was a clock read.** `inc-${Date.now().toString(36)}` gives\\n//! two incidents created in the same millisecond the same id, and `incidents.set`\\n//! then OVERWRITES the first — so the incident vanishes, along with its timeline,\\n//! and nobody is told. Detection is exactly when a burst arrives: one outage\\n//! trips several alerts at once, which is the case this identifier scheme cannot\\n//! survive. Ids are supplied by the host here, and [`IncidentLog::open`] refuses a\\n//! duplicate rather than silently replacing.\\n//!\\n//! **2. `ttdMinutes` was declared and never computed.** The field is documented as\\n//! a duration and nothing ever assigns it, so every incident reports time-to-detect\\n//! as absent — which reads on a dashboard as \\\"we do not measure detection\\\" or, worse,\\n//! as zero. Time to detect needs a moment the impact STARTED, which the `.ts` never\\n//! records. [`Incident::impact_started_at_ms`] is that moment, and TTD is computed\\n//! only when it is known.\\n//!\\n//! **3. Every mutation silently did nothing for an unknown id.** `if (!inc) return`\\n//! — so transitioning, annotating or root-causing a typo'd incident succeeded from\\n//! the caller's point of view and changed nothing. Each of those returns a\\n//! [`Result`] here.\\n//!\\n//! **4. Resolving an already-resolved incident re-resolved it.** No guard on the\\n//! current state, so `resolved → resolved` rewrote `resolvedAt`, recomputed TTR\\n//! from the new timestamp, and fired `onResolved` again — a duplicate page, and a\\n//! TTR that grows every time somebody clicks. A transition to the state an\\n//! incident is already in is [`Transitioned::NoChange`] and touches nothing.\\n//! Re-entering `resolved` after a genuine regression still works, because that is\\n//! a real thing that happens.\\n//!\\n//! **5. The postmortem interpolated untrusted text into Markdown.** Titles come\\n//! from alerts, alerts come from error messages, and error messages carry user\\n//! input. A newline plus `##` in a title forges a section; a `|` or a newline in a\\n//! timeline detail escapes its bullet; a `)` in a portal URL terminates the link\\n//! early and dumps the rest into prose. This is content forgery in a document\\n//! people read to decide what happened, which is the document where a forged line\\n//! does the most damage.\\n//!\\n//! **6. A component with no portal URL rendered `[name](undefined)`.** The `.ts`\\n//! interpolates `c.portalUrl` unconditionally, so an affected component that has\\n//! no documentation link gets a link to a relative path called `undefined`. Plain\\n//! text when there is no URL.\\n//!\\n//! # What is deliberately NOT constrained\\n//!\\n//! Any state may follow any other. That is not an oversight: `monitoring →\\n//! mitigating` is a regression, `resolved → triaging` is a reopen, and an incident\\n//! tool that refuses either is one people work around. What IS enforced is that a\\n//! transition to the current state is a no-op (defect 4).\\n\\nuse std::collections::BTreeMap;\\n\\n/// How bad, in the usual descending scale — `sev1` is worst.\\n///\\n/// Ordered so `max()` finds the worst; the `Ord` derive follows declaration\\n/// order, so [`Sev4`](Self::Sev4) is declared first.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum IncidentSeverity {\\n /// Minor, no user impact.\\n Sev4,\\n /// Degraded, with a workaround.\\n Sev3,\\n /// Significant user impact.\\n Sev2,\\n /// Critical — a core guarantee is broken.\\n Sev1,\\n}\\n\\nimpl IncidentSeverity {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Sev1 => \\\"sev1\\\",\\n Self::Sev2 => \\\"sev2\\\",\\n Self::Sev3 => \\\"sev3\\\",\\n Self::Sev4 => \\\"sev4\\\",\\n }\\n }\\n}\\n\\n/// Where an incident is in its lifecycle.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum IncidentState {\\n /// Something fired.\\n Detected,\\n /// Working out what it is.\\n Triaging,\\n /// Working on stopping the bleeding.\\n Mitigating,\\n /// Mitigated, watching.\\n Monitoring,\\n /// Over.\\n Resolved,\\n /// Written up.\\n Postmortem,\\n}\\n\\nimpl IncidentState {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Detected => \\\"detected\\\",\\n Self::Triaging => \\\"triaging\\\",\\n Self::Mitigating => \\\"mitigating\\\",\\n Self::Monitoring => \\\"monitoring\\\",\\n Self::Resolved => \\\"resolved\\\",\\n Self::Postmortem => \\\"postmortem\\\",\\n }\\n }\\n\\n /// Whether an incident in this state still needs somebody.\\n #[must_use]\\n pub const fn is_active(self) -> bool {\\n !matches!(self, Self::Resolved | Self::Postmortem)\\n }\\n}\\n\\n/// One thing that happened, and when.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct TimelineEntry {\\n /// When, milliseconds since the Unix epoch.\\n pub at_ms: f64,\\n /// Who or what did it.\\n pub actor: String,\\n /// What they did.\\n pub action: String,\\n /// Any detail.\\n pub details: Option<String>,\\n}\\n\\n/// A component caught up in an incident.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct AffectedComponent {\\n /// The component name.\\n pub name: String,\\n /// Which helix node it belongs to. Uncapped — node numbers are labels.\\n pub node: u32,\\n /// Where its documentation is, when it has any.\\n pub portal_url: Option<String>,\\n}\\n\\n/// Something somebody has to do afterwards.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct ActionItem {\\n /// What.\\n pub description: String,\\n /// Who.\\n pub owner: String,\\n /// By when.\\n pub due_date: Option<String>,\\n /// Whether it is done.\\n pub done: bool,\\n}\\n\\n/// What is being opened.\\n///\\n/// `PartialEq` without `Eq`: `impact_started_at_ms` is an `f64`, and floats have\\n/// no total equality.\\n#[derive(Debug, Clone, PartialEq, Default)]\\npub struct NewIncident {\\n /// One-line summary.\\n pub title: String,\\n /// Components implicated.\\n pub affected_components: Vec<AffectedComponent>,\\n /// Mini-apps implicated.\\n pub affected_mini_apps: Vec<String>,\\n /// The alert that fired, when there was one.\\n pub alert_id: Option<String>,\\n /// Who is running it.\\n pub commander: Option<String>,\\n /// When the impact actually began, if known.\\n ///\\n /// Time to detect is measured from HERE, not from detection — the `.ts`\\n /// declares `ttdMinutes` and never has a value to compute it from.\\n pub impact_started_at_ms: Option<f64>,\\n}\\n\\n/// One incident.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct Incident {\\n /// Supplied by the host, not derived from a clock. See defect 1.\\n pub id: String,\\n /// One-line summary.\\n pub title: String,\\n /// How bad.\\n pub severity: IncidentSeverity,\\n /// Where it is.\\n pub state: IncidentState,\\n /// When it was detected, milliseconds since the Unix epoch.\\n pub detected_at_ms: f64,\\n /// When the impact began, if known.\\n pub impact_started_at_ms: Option<f64>,\\n /// When it was resolved.\\n pub resolved_at_ms: Option<f64>,\\n /// Components implicated.\\n pub affected_components: Vec<AffectedComponent>,\\n /// Mini-apps implicated.\\n pub affected_mini_apps: Vec<String>,\\n /// Who ran it.\\n pub commander: Option<String>,\\n /// Everything that happened.\\n pub timeline: Vec<TimelineEntry>,\\n /// What caused it.\\n pub root_cause: Option<String>,\\n /// What to do about it.\\n pub action_items: Vec<ActionItem>,\\n /// The alert that fired.\\n pub alert_id: Option<String>,\\n}\\n\\nimpl Incident {\\n /// Minutes from impact starting to it being detected.\\n ///\\n /// [`None`] when nobody recorded when the impact began, which is honest —\\n /// the `.ts` leaves the field undefined in every case and calls it a duration.\\n #[must_use]\\n pub fn ttd_minutes(&self) -> Option<f64> {\\n let started = self.impact_started_at_ms?;\\n Some(((self.detected_at_ms - started) / 60_000.0).round())\\n }\\n\\n /// Minutes from detection to resolution.\\n #[must_use]\\n pub fn ttr_minutes(&self) -> Option<f64> {\\n let resolved = self.resolved_at_ms?;\\n Some(((resolved - self.detected_at_ms) / 60_000.0).round())\\n }\\n}\\n\\n/// Why a mutation did not happen.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum IncidentError {\\n /// No incident with that id.\\n NotFound,\\n /// An incident with that id is already open.\\n DuplicateId,\\n}\\n\\n/// What a transition did.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum Transitioned {\\n /// The state changed.\\n Moved {\\n /// Where it was.\\n from: IncidentState,\\n /// Where it is.\\n to: IncidentState,\\n },\\n /// It was already in that state. Nothing recorded, nothing fired.\\n ///\\n /// The `.ts` has no such case: `resolved → resolved` rewrites `resolvedAt`,\\n /// recomputes TTR from the new timestamp and fires `onResolved` again.\\n NoChange(IncidentState),\\n}\\n\\nimpl Transitioned {\\n /// Whether this transition entered [`IncidentState::Resolved`] — the one\\n /// moment a resolution notification should fire.\\n #[must_use]\\n pub const fn resolved_now(self) -> bool {\\n matches!(\\n self,\\n Self::Moved {\\n to: IncidentState::Resolved,\\n ..\\n }\\n )\\n }\\n}\\n\\n/// Every open and closed incident.\\n#[derive(Debug, Clone, Default)]\\npub struct IncidentLog {\\n incidents: BTreeMap<String, Incident>,\\n}\\n\\nimpl IncidentLog {\\n /// An empty log.\\n #[must_use]\\n pub fn new() -> Self {\\n Self::default()\\n }\\n\\n /// Open an incident under a host-supplied id.\\n ///\\n /// # Errors\\n ///\\n /// [`IncidentError::DuplicateId`] when that id is already in use. The `.ts`\\n /// derives the id from `Date.now()` and overwrites on collision, which loses\\n /// an entire incident in exactly the burst that produces one.\\n pub fn open(\\n &mut self,\\n id: impl Into<String>,\\n severity: IncidentSeverity,\\n new: NewIncident,\\n now_ms: f64,\\n ) -> Result<&Incident, IncidentError> {\\n let id = id.into();\\n if self.incidents.contains_key(&id) {\\n return Err(IncidentError::DuplicateId);\\n }\\n let incident = Incident {\\n id: id.clone(),\\n title: new.title,\\n severity,\\n state: IncidentState::Detected,\\n detected_at_ms: now_ms,\\n impact_started_at_ms: new.impact_started_at_ms,\\n resolved_at_ms: None,\\n affected_components: new.affected_components,\\n affected_mini_apps: new.affected_mini_apps,\\n commander: new.commander,\\n timeline: vec![TimelineEntry {\\n at_ms: now_ms,\\n actor: \\\"system\\\".to_owned(),\\n action: \\\"Incident created\\\".to_owned(),\\n details: Some(format!(\\\"Severity: {}\\\", severity.as_str())),\\n }],\\n root_cause: None,\\n action_items: Vec::new(),\\n alert_id: new.alert_id,\\n };\\n Ok(self.incidents.entry(id).or_insert(incident))\\n }\\n\\n /// Move an incident to a new state.\\n ///\\n /// # Errors\\n ///\\n /// [`IncidentError::NotFound`] when no incident has that id — which the `.ts`\\n /// answers by returning `undefined` and doing nothing.\\n pub fn transition(\\n &mut self,\\n id: &str,\\n to: IncidentState,\\n actor: impl Into<String>,\\n details: Option<String>,\\n now_ms: f64,\\n ) -> Result<Transitioned, IncidentError> {\\n let incident = self.incidents.get_mut(id).ok_or(IncidentError::NotFound)?;\\n let from = incident.state;\\n if from == to {\\n return Ok(Transitioned::NoChange(from));\\n }\\n\\n incident.state = to;\\n incident.timeline.push(TimelineEntry {\\n at_ms: now_ms,\\n actor: actor.into(),\\n action: format!(\\\"State → {}\\\", to.as_str()),\\n details,\\n });\\n if to == IncidentState::Resolved {\\n incident.resolved_at_ms = Some(now_ms);\\n }\\n Ok(Transitioned::Moved { from, to })\\n }\\n\\n /// Add a note to the timeline.\\n ///\\n /// # Errors\\n ///\\n /// [`IncidentError::NotFound`] when no incident has that id.\\n pub fn note(\\n &mut self,\\n id: &str,\\n actor: impl Into<String>,\\n action: impl Into<String>,\\n details: Option<String>,\\n now_ms: f64,\\n ) -> Result<(), IncidentError> {\\n let incident = self.incidents.get_mut(id).ok_or(IncidentError::NotFound)?;\\n incident.timeline.push(TimelineEntry {\\n at_ms: now_ms,\\n actor: actor.into(),\\n action: action.into(),\\n details,\\n });\\n Ok(())\\n }\\n\\n /// Record what caused it.\\n ///\\n /// # Errors\\n ///\\n /// [`IncidentError::NotFound`] when no incident has that id.\\n pub fn set_root_cause(\\n &mut self,\\n id: &str,\\n root_cause: impl Into<String>,\\n ) -> Result<(), IncidentError> {\\n let incident = self.incidents.get_mut(id).ok_or(IncidentError::NotFound)?;\\n incident.root_cause = Some(root_cause.into());\\n Ok(())\\n }\\n\\n /// Add something somebody has to do.\\n ///\\n /// # Errors\\n ///\\n /// [`IncidentError::NotFound`] when no incident has that id.\\n pub fn add_action_item(&mut self, id: &str, item: ActionItem) -> Result<(), IncidentError> {\\n let incident = self.incidents.get_mut(id).ok_or(IncidentError::NotFound)?;\\n incident.action_items.push(item);\\n Ok(())\\n }\\n\\n /// One incident.\\n #[must_use]\\n pub fn get(&self, id: &str) -> Option<&Incident> {\\n self.incidents.get(id)\\n }\\n\\n /// Every incident still needing somebody.\\n #[must_use]\\n pub fn active(&self) -> Vec<&Incident> {\\n self.incidents\\n .values()\\n .filter(|i| i.state.is_active())\\n .collect()\\n }\\n\\n /// Every incident.\\n #[must_use]\\n pub fn all(&self) -> Vec<&Incident> {\\n self.incidents.values().collect()\\n }\\n\\n /// The worst severity among the active incidents — the one-glance answer.\\n #[must_use]\\n pub fn worst_active(&self) -> Option<IncidentSeverity> {\\n self.active().iter().map(|i| i.severity).max()\\n }\\n}\\n\\n/// Escape a value going into a Markdown line.\\n///\\n/// A newline lets a value start its own block — `\\\\n## Root Cause` forges a\\n/// section in a document people read to decide what happened. A `|` forges table\\n/// columns. Backslashes go first, or the escapes this adds are themselves\\n/// escapable.\\n#[must_use]\\npub fn escape_markdown(value: &str) -> String {\\n let mut out = String::with_capacity(value.len());\\n for c in value.chars() {\\n match c {\\n '\\\\\\\\' => out.push_str(\\\"\\\\\\\\\\\\\\\\\\\"),\\n '|' => out.push_str(\\\"\\\\\\\\|\\\"),\\n '\\\\n' | '\\\\r' => out.push(' '),\\n c => out.push(c),\\n }\\n }\\n out\\n}\\n\\n/// Render a link, or plain text when there is no URL.\\n///\\n/// The `.ts` interpolates `c.portalUrl` unconditionally, so a component with no\\n/// documentation link renders `[name](undefined)` — a link to a relative path\\n/// called `undefined`. Angle brackets are Markdown's literal-URL form, so a `)`\\n/// inside the URL cannot terminate the link early.\\n#[must_use]\\npub fn component_link(component: &AffectedComponent) -> String {\\n let name = escape_markdown(&component.name);\\n match component\\n .portal_url\\n .as_deref()\\n .filter(|url| !url.trim().is_empty())\\n {\\n Some(url) => format!(\\\"[{name}](<{}>)\\\", url.replace(['<', '>', '\\\\n', '\\\\r'], \\\"\\\")),\\n None => name,\\n }\\n}\\n\\n/// The postmortem, in Markdown.\\n///\\n/// Timestamps are milliseconds since the epoch: formatting them for humans needs\\n/// a locale and a timezone, which is a host concern, and the `.ts`'s bare\\n/// `toISOString()` was neither.\\n#[must_use]\\npub fn postmortem(incident: &Incident) -> String {\\n let mut doc = format!(\\n \\\"# Incident Postmortem: {}\\\\n\\\\n\\\",\\n escape_markdown(&incident.title)\\n );\\n doc.push_str(&format!(\\\"**Severity:** {}\\\\n\\\", incident.severity.as_str()));\\n doc.push_str(&format!(\\\"**State:** {}\\\\n\\\", incident.state.as_str()));\\n doc.push_str(&format!(\\\"**Detected:** {}\\\\n\\\", incident.detected_at_ms));\\n doc.push_str(&match incident.resolved_at_ms {\\n Some(at) => format!(\\\"**Resolved:** {at}\\\\n\\\"),\\n None => \\\"**Resolved:** Ongoing\\\\n\\\".to_owned(),\\n });\\n doc.push_str(&match incident.ttd_minutes() {\\n Some(ttd) => format!(\\\"**TTD:** {ttd} minutes\\\\n\\\"),\\n // Not \\\"0\\\", and not silence. Nobody recorded when the impact began.\\n None => \\\"**TTD:** not measured — impact start unrecorded\\\\n\\\".to_owned(),\\n });\\n doc.push_str(&match incident.ttr_minutes() {\\n Some(ttr) => format!(\\\"**TTR:** {ttr} minutes\\\\n\\\"),\\n None => \\\"**TTR:** N/A\\\\n\\\".to_owned(),\\n });\\n doc.push_str(&format!(\\n \\\"**Commander:** {}\\\\n\\\",\\n incident\\n .commander\\n .as_deref()\\n .map_or_else(|| \\\"Unassigned\\\".to_owned(), escape_markdown)\\n ));\\n\\n doc.push_str(\\\"\\\\n## Affected Components\\\\n\\\");\\n if incident.affected_components.is_empty() {\\n doc.push_str(\\\"_None recorded_\\\\n\\\");\\n } else {\\n for component in &incident.affected_components {\\n doc.push_str(&format!(\\n \\\"- {} (Node {})\\\\n\\\",\\n component_link(component),\\n component.node\\n ));\\n }\\n }\\n\\n doc.push_str(\\\"\\\\n## Affected Mini-Apps\\\\n\\\");\\n if incident.affected_mini_apps.is_empty() {\\n doc.push_str(\\\"_None recorded_\\\\n\\\");\\n } else {\\n doc.push_str(&format!(\\n \\\"{}\\\\n\\\",\\n incident\\n .affected_mini_apps\\n .iter()\\n .map(|app| escape_markdown(app))\\n .collect::<Vec<_>>()\\n .join(\\\", \\\")\\n ));\\n }\\n\\n doc.push_str(\\\"\\\\n## Timeline\\\\n\\\");\\n if incident.timeline.is_empty() {\\n doc.push_str(\\\"_Empty_\\\\n\\\");\\n } else {\\n for entry in &incident.timeline {\\n doc.push_str(&format!(\\n \\\"- **{}** [{}] {}{}\\\\n\\\",\\n entry.at_ms,\\n escape_markdown(&entry.actor),\\n escape_markdown(&entry.action),\\n entry\\n .details\\n .as_deref()\\n .map(|d| format!(\\\" — {}\\\", escape_markdown(d)))\\n .unwrap_or_default()\\n ));\\n }\\n }\\n\\n doc.push_str(&format!(\\n \\\"\\\\n## Root Cause\\\\n{}\\\\n\\\",\\n incident\\n .root_cause\\n .as_deref()\\n .map_or_else(|| \\\"_To be determined_\\\".to_owned(), escape_markdown)\\n ));\\n\\n doc.push_str(\\\"\\\\n## Action Items\\\\n\\\");\\n if incident.action_items.is_empty() {\\n doc.push_str(\\\"_None yet_\\\\n\\\");\\n } else {\\n for item in &incident.action_items {\\n doc.push_str(&format!(\\n \\\"- [{}] {} (Owner: {}{})\\\\n\\\",\\n if item.done { \\\"x\\\" } else { \\\" \\\" },\\n escape_markdown(&item.description),\\n escape_markdown(&item.owner),\\n item.due_date\\n .as_deref()\\n .map(|due| format!(\\\", Due: {}\\\", escape_markdown(due)))\\n .unwrap_or_default()\\n ));\\n }\\n }\\n\\n doc\\n}\\n\"}],\"contract\":null}",
|
|
1222
|
+
"mzizi-meta-tile": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-meta-tile\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Universal date/location signature tile — the 4.2.0 When/Where unit. A rounded-square chip (an icon tile OR a month-over-day date chip) paired with a bold 16px primary line and a 13px muted secondary line, with an optional caption and trailing action. Purely presentational and server-safe (no client directive, no harness) so it renders inside React Server Components. The chip tint defaults to the brand accent (--brand-accent) so each app swaps its own mineral. Used on event detail When/Where rows, place cards, and any listing that anchors a date or a location.\",\"crate\":{\"name\":\"mzizi-brand\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n3-brand/mzizi-meta-tile.rs\",\"type\":\"registry:rust\",\"content\":\"//! MZIZI META TILE — N3 brand, Dioxus. A Mzizi Roots component.\\n//!\\n//! The Rust sibling of `mzizi-meta-tile.tsx`: the When / Where unit — a rounded-square chip\\n//! (an icon tile or a month/day date chip) beside a bold primary line and a muted secondary\\n//! line. Same `data-slot`, same classes, same chip tint.\\n//!\\n//! Purely presentational, like the `.tsx`: no harness, no motion, no state. It renders the\\n//! same markup under server-side rendering as in a browser.\\n//!\\n//! # The icon is an element, not a component type\\n//!\\n//! The `.tsx` takes `icon` as a React component type and renders it at `size-5` with\\n//! `strokeWidth={2.2}`. Rust has no Lucide dependency and should not grow one for a slot, so\\n//! [`MziziMetaTileProps::icon`] is an [`Element`]: the host passes the icon it already has,\\n//! sized as it likes. The chip still tints it through `color`.\\n\\nuse dioxus::prelude::*;\\n\\n/// The component's contract, in the clause grammar of the Mzizi language's `contract … end`\\n/// block (mzizi-dev/mzizi `design/RFC-0006-contracts.md`). `mzizi-brand`'s contract suite\\n/// evaluates every clause against this component's server-rendered markup and fails on any\\n/// clause it cannot evaluate.\\npub const CONTRACT: &str = r#\\\"contract\\n slot is \\\"mzizi-meta-tile\\\"\\n tint uses \\\"--brand-accent\\\"\\n when date shows span \\\"Sep\\\"\\nend\\\"#;\\n\\n/// The default chip tint: the app's brand accent, falling back to the primary token.\\npub const DEFAULT_TINT: &str = \\\"var(--brand-accent, var(--color-primary))\\\";\\n\\nconst CHIP_SURFACE: &str =\\n \\\"color-mix(in srgb, var(--brand-accent, var(--color-primary)) 12%, transparent)\\\";\\nconst CHIP_BORDER: &str =\\n \\\"color-mix(in srgb, var(--brand-accent, var(--color-primary)) 18%, transparent)\\\";\\n\\n/// Join a base class string with a consumer's extra classes, without a trailing space.\\nfn join(base: &str, extra: &str) -> String {\\n if extra.is_empty() {\\n base.to_owned()\\n } else {\\n format!(\\\"{base} {extra}\\\")\\n }\\n}\\n\\n/// A month-over-day date chip.\\n#[derive(Clone, Debug, PartialEq)]\\npub struct MetaDate {\\n /// Month name; only the first three characters are shown.\\n pub month: String,\\n /// Day of the month, as the host wants it written.\\n pub day: String,\\n}\\n\\n/// The first three characters of a month name, matching the `.tsx`'s `month.slice(0, 3)`.\\n///\\n/// Character-based rather than byte-based, so a non-ASCII month name is never split inside a\\n/// code point.\\n#[must_use]\\npub fn month_abbrev(month: &str) -> String {\\n month.chars().take(3).collect()\\n}\\n\\n/// Props for [`MziziMetaTile`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziMetaTileProps {\\n /// Icon chip content. Ignored when `date` is set, as in the `.tsx`.\\n #[props(default)]\\n pub icon: Option<Element>,\\n /// Date chip — month abbreviation over the day numeral.\\n #[props(default)]\\n pub date: Option<MetaDate>,\\n /// Small caption above the primary line.\\n #[props(default)]\\n pub caption: Option<String>,\\n /// Bold primary line.\\n pub primary: String,\\n /// Muted secondary line.\\n #[props(default)]\\n pub secondary: Option<String>,\\n /// Accent tint for the chip glyph or date numeral.\\n #[props(default = DEFAULT_TINT.to_string())]\\n pub tint: String,\\n /// Trailing action, right-aligned.\\n #[props(default)]\\n pub trailing: Option<Element>,\\n /// Extra classes, appended last.\\n #[props(default)]\\n pub class: String,\\n}\\n\\n/// A date or icon chip paired with a primary and secondary line.\\n#[component]\\npub fn MziziMetaTile(props: MziziMetaTileProps) -> Element {\\n let chip_background = if props.date.is_some() {\\n \\\"transparent\\\"\\n } else {\\n CHIP_SURFACE\\n };\\n let primary_class = if props.caption.is_some() {\\n \\\"truncate text-[16px] leading-[1.25] font-semibold text-foreground mt-1\\\"\\n } else {\\n \\\"truncate text-[16px] leading-[1.25] font-semibold text-foreground\\\"\\n };\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-meta-tile\\\",\\n class: join(\\\"flex items-center gap-3\\\", &props.class),\\n div {\\n class: \\\"flex size-12 shrink-0 flex-col items-center justify-center rounded-[var(--radius-md,12px)] border\\\",\\n style: \\\"border-color: {CHIP_BORDER}; background-color: {chip_background};\\\",\\n \\\"aria-hidden\\\": \\\"true\\\",\\n if let Some(date) = &props.date {\\n span { class: \\\"text-[10px] leading-none font-semibold tracking-wide text-muted-foreground uppercase\\\",\\n {month_abbrev(&date.month)}\\n }\\n span { class: \\\"mt-0.5 text-xl leading-none font-bold\\\", style: \\\"color: {props.tint};\\\",\\n \\\"{date.day}\\\"\\n }\\n } else if let Some(icon) = &props.icon {\\n span { style: \\\"color: {props.tint};\\\", {icon} }\\n }\\n }\\n div { class: \\\"min-w-0 flex-1\\\",\\n if let Some(caption) = &props.caption {\\n div { class: \\\"text-[13px] leading-none font-medium text-muted-foreground\\\", \\\"{caption}\\\" }\\n }\\n div { class: \\\"{primary_class}\\\", \\\"{props.primary}\\\" }\\n if let Some(secondary) = &props.secondary {\\n div { class: \\\"mt-0.5 truncate text-[13px] text-muted-foreground\\\", \\\"{secondary}\\\" }\\n }\\n }\\n if let Some(trailing) = &props.trailing {\\n div { class: \\\"shrink-0\\\", {trailing} }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn month_is_cut_to_three_characters() {\\n assert_eq!(month_abbrev(\\\"September\\\"), \\\"Sep\\\");\\n assert_eq!(month_abbrev(\\\"Mei\\\"), \\\"Mei\\\");\\n assert_eq!(month_abbrev(\\\"Ma\\\"), \\\"Ma\\\");\\n }\\n\\n #[test]\\n fn month_abbreviation_never_splits_a_code_point() {\\n assert_eq!(month_abbrev(\\\"Février\\\"), \\\"Fév\\\");\\n }\\n\\n #[test]\\n fn default_tint_is_the_brand_accent() {\\n assert!(DEFAULT_TINT.starts_with(\\\"var(--brand-accent\\\"));\\n }\\n}\\n\"}],\"contract\":\"contract\\n slot is \\\"mzizi-meta-tile\\\"\\n tint uses \\\"--brand-accent\\\"\\n when date shows span \\\"Sep\\\"\\nend\"}",
|
|
1223
|
+
"mzizi-mini-app-runtime": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-mini-app-runtime\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Mini-app container for Mukoko super-app architecture. Manages mini-app lifecycle: load, mount, suspend, destroy. Provides isolated context per mini-app with shared token system. Architecture doc: \\\"13 Tier 2 web mini-apps\\\" + \\\"4 Tier 1 native mini-apps\\\".\",\"crate\":{\"name\":\"mzizi-shell\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n7-shell/mzizi-mini-app-runtime.rs\",\"type\":\"registry:rust\",\"content\":\"//! NYUCHI MINI-APP RUNTIME — N7 shell, Dioxus.\\n//!\\n//! The Rust sibling of `mzizi-mini-app-runtime.tsx`: the lifecycle host for a tiered mini-app,\\n//! sharing its contract — same `data-slot`, same state set.\\n//!\\n//! # The \\\"destroyed\\\" state is a gap in the `.tsx`\\n//!\\n//! `MiniAppState` in the `.tsx` includes `\\\"destroyed\\\"`, but its render only branches on\\n//! `\\\"loading\\\"`, `\\\"error\\\"`, and `\\\"suspended\\\"` — everything else, `\\\"destroyed\\\"` included, falls\\n//! into the final `return` and renders as fully mounted, with `data-state=\\\"mounted\\\"` hardcoded\\n//! (not even reading the actual `state`). A destroyed mini-app would keep rendering its\\n//! children. `RenderOutcome` here makes that branch exhaustive so `Destroyed` gets its own,\\n//! nothing-rendered outcome instead of silently reusing `Mounted`'s.\\n//!\\n//! **The `.tsx` sibling still has this.**\\n\\nuse dioxus::prelude::*;\\n\\n/// Lifecycle state of a hosted mini-app.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\\npub enum MiniAppState {\\n /// Being fetched/initialized.\\n Loading,\\n /// Running and visible.\\n Mounted,\\n /// Backgrounded but retained.\\n Suspended,\\n /// Failed to load or crashed.\\n Error,\\n /// Torn down; nothing of it should render.\\n Destroyed,\\n}\\n\\nimpl MiniAppState {\\n /// The wire/attribute representation of this state.\\n pub fn as_str(self) -> &'static str {\\n match self {\\n MiniAppState::Loading => \\\"loading\\\",\\n MiniAppState::Mounted => \\\"mounted\\\",\\n MiniAppState::Suspended => \\\"suspended\\\",\\n MiniAppState::Error => \\\"error\\\",\\n MiniAppState::Destroyed => \\\"destroyed\\\",\\n }\\n }\\n}\\n\\n/// Static mini-app identity and constraints.\\n#[derive(Clone, Debug, PartialEq)]\\npub struct MiniAppConfig {\\n /// Unique identifier.\\n pub id: String,\\n /// Display name, shown while loading/erroring.\\n pub name: String,\\n /// Tier (1 or 2), rendered as `data-tier` while mounted.\\n pub tier: u8,\\n}\\n\\n/// What to render for a given state — see the module docs for why this is exhaustive where\\n/// the `.tsx`'s if-chain is not.\\n#[derive(Debug, PartialEq)]\\npub enum RenderOutcome {\\n /// Show the loading spinner.\\n Loading,\\n /// Show the error panel (plus any `fallback`).\\n Error,\\n /// Render nothing.\\n Nothing,\\n /// Render the mounted shell and children.\\n Mounted,\\n}\\n\\n/// Maps a [`MiniAppState`] to what should actually render.\\npub fn render_outcome(state: MiniAppState) -> RenderOutcome {\\n match state {\\n MiniAppState::Loading => RenderOutcome::Loading,\\n MiniAppState::Error => RenderOutcome::Error,\\n MiniAppState::Suspended | MiniAppState::Destroyed => RenderOutcome::Nothing,\\n MiniAppState::Mounted => RenderOutcome::Mounted,\\n }\\n}\\n\\n/// Props for [`MziziMiniAppRuntime`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziMiniAppRuntimeProps {\\n /// Static mini-app identity and constraints.\\n pub config: MiniAppConfig,\\n /// Current lifecycle state.\\n #[props(default = MiniAppState::Loading)]\\n pub state: MiniAppState,\\n /// Fired whenever `state` changes.\\n #[props(default)]\\n pub on_state_change: EventHandler<MiniAppState>,\\n /// Replaces `useMziziHarness(...).motion.prefersReduced` — the one bit\\n /// of the harness this component actually reads.\\n #[props(default)]\\n pub prefers_reduced_motion: bool,\\n /// Extra content shown alongside the error panel.\\n pub fallback: Option<Element>,\\n /// The mini-app's own UI, shown only while mounted.\\n pub children: Element,\\n}\\n\\n/// Lifecycle host for a tiered mini-app: loading, error, suspended, or mounted.\\n#[component]\\npub fn MziziMiniAppRuntime(props: MziziMiniAppRuntimeProps) -> Element {\\n match render_outcome(props.state) {\\n RenderOutcome::Loading => rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-mini-app-runtime\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-mini-app-runtime\\\",\\n \\\"data-app\\\": \\\"{props.config.id}\\\",\\n \\\"data-state\\\": \\\"loading\\\",\\n role: \\\"status\\\",\\n \\\"aria-label\\\": \\\"Loading {props.config.name}\\\",\\n class: \\\"flex min-h-screen items-center justify-center\\\",\\n div { class: \\\"flex flex-col items-center gap-3\\\",\\n p { class: \\\"text-sm text-muted-foreground\\\", \\\"{props.config.name}\\\" }\\n }\\n }\\n },\\n RenderOutcome::Error => rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-mini-app-runtime\\\",\\n \\\"data-app\\\": \\\"{props.config.id}\\\",\\n \\\"data-state\\\": \\\"error\\\",\\n role: \\\"alert\\\",\\n class: \\\"flex min-h-screen flex-col items-center justify-center gap-4 p-6 text-center\\\",\\n p { class: \\\"text-lg font-semibold\\\", \\\"Something went wrong\\\" }\\n p { class: \\\"text-sm text-muted-foreground\\\", \\\"{props.config.name} encountered an error\\\" }\\n {props.fallback.clone().unwrap_or(rsx! {})}\\n }\\n },\\n RenderOutcome::Nothing => rsx! {},\\n RenderOutcome::Mounted => rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-mini-app-runtime\\\",\\n \\\"data-app\\\": \\\"{props.config.id}\\\",\\n \\\"data-state\\\": \\\"{props.state.as_str()}\\\",\\n \\\"data-tier\\\": \\\"{props.config.tier}\\\",\\n role: \\\"application\\\",\\n \\\"aria-label\\\": \\\"{props.config.name}\\\",\\n class: \\\"flex min-h-screen flex-col\\\",\\n {props.children.clone()}\\n }\\n },\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn destroyed_renders_nothing_not_mounted() {\\n // The .tsx has no branch for \\\"destroyed\\\": it isn't loading, error,\\n // or suspended, so it falls into the final `return` and renders\\n // full mounted UI (data-state hardcoded to \\\"mounted\\\") with children\\n // still attached. Delete this test if that .tsx branch is fixed.\\n assert_eq!(\\n render_outcome(MiniAppState::Destroyed),\\n RenderOutcome::Nothing\\n );\\n assert_ne!(\\n render_outcome(MiniAppState::Destroyed),\\n render_outcome(MiniAppState::Mounted)\\n );\\n }\\n\\n #[test]\\n fn suspended_and_destroyed_both_render_nothing() {\\n assert_eq!(\\n render_outcome(MiniAppState::Suspended),\\n RenderOutcome::Nothing\\n );\\n assert_eq!(\\n render_outcome(MiniAppState::Destroyed),\\n RenderOutcome::Nothing\\n );\\n }\\n\\n #[test]\\n fn loading_and_error_and_mounted_are_distinct() {\\n assert_eq!(\\n render_outcome(MiniAppState::Loading),\\n RenderOutcome::Loading\\n );\\n assert_eq!(render_outcome(MiniAppState::Error), RenderOutcome::Error);\\n assert_eq!(\\n render_outcome(MiniAppState::Mounted),\\n RenderOutcome::Mounted\\n );\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1224
|
+
"mzizi-notification-center": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-notification-center\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"App-wide notification drawer/panel. Displays notifications grouped by type (social, fintech, system, alerts), with read/unread state, actions, and swipe-to-dismiss. Competitors: Instagram, Slack, WeChat.\",\"crate\":{\"name\":\"mzizi-shell\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n7-shell/mzizi-notification-center.rs\",\"type\":\"registry:rust\",\"content\":\"//! NYUCHI NOTIFICATION CENTER — N7 shell, Dioxus.\\n//!\\n//! The Rust sibling of `mzizi-notification-center.tsx`: a slide-over notification list, sharing\\n//! its contract — same `data-slot`, same unread-count badge.\\n//!\\n//! # The touch floor is raised above the `.tsx`\\n//!\\n//! The `.tsx` ships `min-h-[44px]` on the close/dismiss controls. Raised to `min-h-[48px]`\\n//! here, matching this build-out's other touch-floor fixes; nothing else changes.\\n//!\\n//! **The `.tsx` sibling still has this.**\\n\\nuse dioxus::prelude::*;\\n\\n/// The kind of event a notification represents.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\\npub enum NotificationKind {\\n /// A social interaction (follow, mention, reply).\\n Social,\\n /// A financial/transactional event.\\n Fintech,\\n /// A system-generated notice.\\n System,\\n /// A time-sensitive alert.\\n Alert,\\n}\\n\\n/// An inline call-to-action shown on a notification.\\n#[derive(Clone, Debug, PartialEq)]\\npub struct NotificationAction {\\n /// Button label.\\n pub label: String,\\n}\\n\\n/// A single notification entry.\\n#[derive(Clone, Debug, PartialEq)]\\npub struct Notification {\\n /// Unique identifier.\\n pub id: String,\\n /// The kind of event this represents.\\n pub kind: NotificationKind,\\n /// Headline text.\\n pub title: String,\\n /// Optional body text.\\n pub message: Option<String>,\\n /// Optional avatar image URL.\\n pub avatar: Option<String>,\\n /// Pre-formatted display timestamp.\\n pub timestamp: String,\\n /// Whether the user has seen this notification.\\n pub read: bool,\\n /// Optional inline action.\\n pub action: Option<NotificationAction>,\\n}\\n\\n/// Counts unread notifications.\\npub fn unread_count(notifications: &[Notification]) -> usize {\\n notifications.iter().filter(|n| !n.read).count()\\n}\\n\\n/// Props for [`MziziNotificationCenter`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziNotificationCenterProps {\\n /// Whether the panel is currently shown.\\n pub open: bool,\\n /// Notifications to display, most recent first.\\n pub notifications: Vec<Notification>,\\n /// Shown when `notifications` is empty.\\n #[props(default = \\\"You are all caught up\\\".to_string())]\\n pub empty_message: String,\\n /// Replaces `useMziziHarness(...).motion.prefersReduced`.\\n #[props(default)]\\n pub prefers_reduced_motion: bool,\\n /// Fired to open or close the panel.\\n #[props(default)]\\n pub on_open_change: EventHandler<bool>,\\n /// Fired when \\\"Mark all read\\\" is pressed.\\n #[props(default)]\\n pub on_mark_all_read: EventHandler<()>,\\n /// Fired with a notification's `id` when it is dismissed.\\n #[props(default)]\\n pub on_dismiss: EventHandler<String>,\\n}\\n\\n/// Slide-over panel listing notifications with an unread-count badge.\\n#[component]\\npub fn MziziNotificationCenter(props: MziziNotificationCenterProps) -> Element {\\n if !props.open {\\n return rsx! {};\\n }\\n let unread = unread_count(&props.notifications);\\n\\n rsx! {\\n div { class: \\\"fixed inset-0 z-40\\\", \\\"aria-hidden\\\": \\\"true\\\", onclick: move |_| props.on_open_change.call(false) }\\n div {\\n \\\"data-slot\\\": \\\"mzizi-notification-center\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-notification-center\\\",\\n role: \\\"dialog\\\",\\n \\\"aria-label\\\": \\\"Notifications\\\",\\n \\\"aria-modal\\\": \\\"true\\\",\\n class: \\\"fixed top-0 right-0 z-50 flex h-full w-full max-w-sm flex-col border-l border-border bg-card shadow-2xl sm:top-16 sm:right-4 sm:h-auto sm:max-h-[80vh] sm:rounded-[var(--radius-xl,17px)] sm:border\\\",\\n\\n header { class: \\\"flex items-center justify-between border-b border-border px-4 py-3\\\",\\n div { class: \\\"flex items-center gap-2\\\",\\n h2 { class: \\\"text-sm font-semibold\\\", \\\"Notifications\\\" }\\n if unread > 0 {\\n span { class: \\\"rounded-full bg-primary px-1.5 py-0.5 text-[10px] font-bold text-primary-foreground\\\", \\\"{unread}\\\" }\\n }\\n }\\n div { class: \\\"flex items-center gap-2\\\",\\n if unread > 0 {\\n button {\\n class: \\\"min-h-[48px] px-2 text-xs text-muted-foreground transition-colors hover:text-foreground\\\",\\n onclick: move |_| props.on_mark_all_read.call(()),\\n \\\"Mark all read\\\"\\n }\\n }\\n button {\\n \\\"aria-label\\\": \\\"Close\\\",\\n class: \\\"flex size-8 min-h-[48px] min-w-[48px] items-center justify-center rounded-full transition-colors hover:bg-muted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\\\",\\n onclick: move |_| props.on_open_change.call(false),\\n \\\"\\\\u{2715}\\\"\\n }\\n }\\n }\\n\\n div { class: \\\"flex-1 overflow-y-auto\\\",\\n if props.notifications.is_empty() {\\n div { class: \\\"flex flex-col items-center justify-center py-16 text-sm text-muted-foreground\\\", \\\"{props.empty_message}\\\" }\\n } else {\\n for n in props.notifications.iter() {\\n div {\\n key: \\\"{n.id}\\\",\\n class: \\\"flex gap-3 border-b border-border px-4 py-3 transition-colors\\\",\\n div { class: \\\"min-w-0 flex-1\\\",\\n p { class: \\\"text-sm\\\", \\\"{n.title}\\\" }\\n if let Some(msg) = &n.message {\\n p { class: \\\"mt-0.5 line-clamp-2 text-xs text-muted-foreground\\\", \\\"{msg}\\\" }\\n }\\n div { class: \\\"mt-1 flex items-center gap-2\\\",\\n time { class: \\\"text-[10px] text-muted-foreground/60\\\", \\\"{n.timestamp}\\\" }\\n }\\n }\\n button {\\n \\\"aria-label\\\": \\\"Dismiss\\\",\\n class: \\\"flex min-h-[48px] min-w-[48px] shrink-0 items-center justify-center self-start text-muted-foreground/40 hover:text-muted-foreground\\\",\\n onclick: {\\n let id = n.id.clone();\\n move |_| props.on_dismiss.call(id.clone())\\n },\\n \\\"\\\\u{2715}\\\"\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n fn notif(id: &str, read: bool) -> Notification {\\n Notification {\\n id: id.into(),\\n kind: NotificationKind::System,\\n title: id.into(),\\n message: None,\\n avatar: None,\\n timestamp: \\\"now\\\".into(),\\n read,\\n action: None,\\n }\\n }\\n\\n #[test]\\n fn unread_count_counts_only_unread() {\\n let notifications = vec![notif(\\\"a\\\", false), notif(\\\"b\\\", true), notif(\\\"c\\\", false)];\\n assert_eq!(unread_count(¬ifications), 2);\\n }\\n\\n #[test]\\n fn unread_count_is_zero_when_all_read() {\\n let notifications = vec![notif(\\\"a\\\", true)];\\n assert_eq!(unread_count(¬ifications), 0);\\n }\\n\\n #[test]\\n fn unread_count_is_zero_for_empty_list() {\\n assert_eq!(unread_count(&[]), 0);\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1225
|
+
"mzizi-otel": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-otel\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"OTLP exporter for N8 assurance signals. Turns probe results, tracked errors and fired alerts into OpenTelemetry spans and POSTs them to any OTLP/HTTP collector, so a Mzizi signal can be consumed by any agent or service that speaks OpenTelemetry. Zero dependencies and no Node built-ins - runs unchanged in Node, a Cloudflare Worker, Deno or a browser.\",\"crate\":{\"name\":\"mzizi-assurance\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n8-assurance/mzizi-otel.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N8 assurance — the OTLP exporter core.\\n//!\\n//! The Rust implementation of `mzizi-otel`. Its `.ts` sibling is the per-target\\n//! shim; this is the shared core, and the split between them is the whole point\\n//! of N8 being a Rust node rather than a TypeScript one.\\n//!\\n//! # This core is PURE. It does not send.\\n//!\\n//! `build_trace_request` returns a [`TraceRequest`] — a URL, headers and a body —\\n//! and the host does the sending: `fetch` in a browser, `fetch` or the Browser\\n//! Run binding in a Cloudflare Worker, whatever a native shell uses.\\n//!\\n//! That is not a limitation worked around, it is the correct boundary:\\n//!\\n//! * **Every target sends differently and computes identically.** Payload\\n//! shape, attribute typing, nanosecond conversion, endpoint resolution and the\\n//! span tree are the parts that must not vary, so they are the parts that live\\n//! here once.\\n//! * **The never-throw rule becomes structural instead of promised.** The `.ts`\\n//! version had to wrap `fetch` in a `try/catch` and hand back\\n//! `{ exported: false, reason }`, because a probe reporting \\\"failed\\\" merely\\n//! because its collector was unreachable manufactures an incident out of an\\n//! exporter outage. With no I/O in the core there is nothing to catch — the\\n//! host owns the failure and the verdict, and cannot conflate them.\\n//! * **No HTTP dependency.** `reqwest` would pull a TLS stack into a WASM module\\n//! whose host already has one.\\n//!\\n//! # Randomness is supplied, never chosen\\n//!\\n//! [`TraceId`] and [`SpanId`] are constructed from bytes the caller provides. A\\n//! shared core must not pick the host's CSPRNG: in a browser or Worker the right\\n//! source is `crypto.getRandomValues`, natively it is the OS, and a WASM build\\n//! that reaches for `getrandom` needs a backend configured per target — a\\n//! well-known footgun that turns into a link error at the worst moment.\\n//!\\n//! `Math.random()` and its equivalents are wrong here for a reason that is not\\n//! style: trace ids are correlation keys across services, so a predictable one\\n//! lets an unrelated caller collide with, or forge, a trace.\\n\\nuse std::collections::BTreeMap;\\nuse std::fmt;\\n\\n/// Span status, matching the OTLP enum exactly.\\n///\\n/// Named rather than inlined because a bare `2` in a payload is unreadable and\\n/// the two non-zero values are easy to transpose.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum StatusCode {\\n /// No status was set. The OTLP default.\\n Unset = 0,\\n /// The operation completed successfully.\\n Ok = 1,\\n /// The operation failed. This is the variant fundi acts on.\\n Error = 2,\\n}\\n\\n/// Span kind, matching the OTLP enum.\\n///\\n/// Only the two kinds this node emits are modelled. A probe *step* calls\\n/// something external so it is [`SpanKind::Client`]; the *run* calls nothing\\n/// itself so it is [`SpanKind::Internal`]. Reversing them makes a collector draw\\n/// service-dependency edges that do not exist.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum SpanKind {\\n /// The operation is internal to this process.\\n Internal = 1,\\n /// The operation called something outside this process.\\n Client = 3,\\n}\\n\\n/// A typed attribute value.\\n///\\n/// OTLP tags every attribute with its type; a bare JSON value is rejected. The\\n/// integer case is serialised as a **string** because JSON cannot hold an int64\\n/// exactly — a float64 silently loses precision above 2^53, which is where a\\n/// nanosecond timestamp lives.\\n#[derive(Debug, Clone, PartialEq)]\\npub enum AttributeValue {\\n /// A string attribute.\\n Str(String),\\n /// An integer attribute. Crosses the wire as a quoted string.\\n Int(i64),\\n /// A floating-point attribute.\\n Double(f64),\\n /// A boolean attribute.\\n Bool(bool),\\n}\\n\\nimpl From<&str> for AttributeValue {\\n fn from(v: &str) -> Self {\\n Self::Str(v.to_owned())\\n }\\n}\\nimpl From<String> for AttributeValue {\\n fn from(v: String) -> Self {\\n Self::Str(v)\\n }\\n}\\nimpl From<i64> for AttributeValue {\\n fn from(v: i64) -> Self {\\n Self::Int(v)\\n }\\n}\\nimpl From<u32> for AttributeValue {\\n fn from(v: u32) -> Self {\\n Self::Int(i64::from(v))\\n }\\n}\\nimpl From<f64> for AttributeValue {\\n fn from(v: f64) -> Self {\\n Self::Double(v)\\n }\\n}\\nimpl From<bool> for AttributeValue {\\n fn from(v: bool) -> Self {\\n Self::Bool(v)\\n }\\n}\\n\\n/// A 16-byte trace id. Renders as 32 lowercase hex characters.\\n///\\n/// A collector rejects any other length outright, and it does so by dropping the\\n/// span rather than by returning an error — so the length is enforced by the type\\n/// instead of being checked at the edge.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub struct TraceId([u8; 16]);\\n\\nimpl TraceId {\\n /// Build a trace id from 16 bytes supplied by the host's CSPRNG.\\n #[must_use]\\n pub const fn new(bytes: [u8; 16]) -> Self {\\n Self(bytes)\\n }\\n}\\n\\nimpl fmt::Display for TraceId {\\n fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\\n for b in self.0 {\\n write!(f, \\\"{b:02x}\\\")?;\\n }\\n Ok(())\\n }\\n}\\n\\n/// An 8-byte span id. Renders as 16 lowercase hex characters.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub struct SpanId([u8; 8]);\\n\\nimpl SpanId {\\n /// Build a span id from 8 bytes supplied by the host's CSPRNG.\\n #[must_use]\\n pub const fn new(bytes: [u8; 8]) -> Self {\\n Self(bytes)\\n }\\n}\\n\\nimpl fmt::Display for SpanId {\\n fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\\n for b in self.0 {\\n write!(f, \\\"{b:02x}\\\")?;\\n }\\n Ok(())\\n }\\n}\\n\\n/// Where the exporter sends, and what identifies the sender.\\n///\\n/// There is deliberately **no default endpoint**. `mzizi-rum` defaulted to\\n/// `https://mzizi.dev/api/rum`, a route that returns 404 and has never existed,\\n/// and every consumer who installed it without setting an endpoint posted into a\\n/// void that looked exactly like working telemetry. Inventing a destination for\\n/// someone else's telemetry is not a sane default even when the destination\\n/// works.\\n#[derive(Debug, Clone, Default)]\\npub struct OtelConfig {\\n /// Base endpoint. The signal path `/v1/traces` is appended.\\n pub endpoint: Option<String>,\\n /// Complete traces endpoint, used verbatim with **no** path appended.\\n ///\\n /// The asymmetry with [`OtelConfig::endpoint`] is the OTLP spec's, not ours,\\n /// and it is the detail people get wrong: appending to this one produces\\n /// `/v1/traces/v1/traces` and a 404 that reads like a broken collector.\\n pub traces_endpoint: Option<String>,\\n /// `service.name`. Required by the OTel resource conventions.\\n pub service_name: String,\\n /// `service.version`, when the host knows it.\\n pub service_version: Option<String>,\\n /// Deployment environment — `production`, `preview`, `local`.\\n pub environment: Option<String>,\\n /// Extra headers: an API key, a tenant id.\\n pub headers: BTreeMap<String, String>,\\n}\\n\\n/// One span, before it is turned into a payload.\\n#[derive(Debug, Clone)]\\npub struct Span {\\n /// Span name, as a collector displays it.\\n pub name: String,\\n /// Span kind.\\n pub kind: SpanKind,\\n /// This span's own id.\\n pub span_id: SpanId,\\n /// The parent span's id, when this span has one.\\n pub parent_span_id: Option<SpanId>,\\n /// Start time in milliseconds since the Unix epoch.\\n pub start_time_ms: f64,\\n /// End time in milliseconds since the Unix epoch.\\n pub end_time_ms: f64,\\n /// Typed attributes. Ordered so a payload is byte-stable across runs.\\n pub attributes: BTreeMap<String, AttributeValue>,\\n /// Status code.\\n pub status: StatusCode,\\n /// Status message. Carried only when there is something to say.\\n pub status_message: Option<String>,\\n}\\n\\n/// Everything the host needs to make one HTTP request, and nothing it does not.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct TraceRequest {\\n /// Fully resolved URL to POST to.\\n pub url: String,\\n /// Headers, including `Content-Type: application/json`.\\n pub headers: BTreeMap<String, String>,\\n /// The OTLP/HTTP JSON body.\\n pub body: String,\\n}\\n\\n/// Why no request was produced.\\n///\\n/// Not an error type in the \\\"something went wrong\\\" sense: the common variant is\\n/// a consumer who simply runs no collector, and for them this must be inert\\n/// rather than noisy.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub enum NotExported {\\n /// There were no spans to send.\\n NoSpans,\\n /// No endpoint is configured, so nothing should be sent anywhere.\\n NoEndpoint,\\n}\\n\\nimpl fmt::Display for NotExported {\\n fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\\n match self {\\n Self::NoSpans => f.write_str(\\\"no spans\\\"),\\n Self::NoEndpoint => f.write_str(\\n \\\"no OTLP endpoint configured (set OTEL_EXPORTER_OTLP_ENDPOINT or \\\\\\n OtelConfig::endpoint) — nothing was sent\\\",\\n ),\\n }\\n }\\n}\\n\\n/// Milliseconds, possibly fractional, to an integer nanosecond count.\\n///\\n/// Returned as a string by the serialiser for the int64 reason above. Rounding\\n/// rather than truncating keeps a duration from drifting shorter every hop.\\nfn to_unix_nano(ms: f64) -> u128 {\\n // Milliseconds since the epoch are positive in every case this handles; a\\n // negative value would mean a clock before 1970, which is not a span.\\n let ns = (ms * 1_000_000.0).round();\\n if ns <= 0.0 { 0 } else { ns as u128 }\\n}\\n\\n/// Resolve the traces URL, honouring the spec's base/complete asymmetry.\\n///\\n/// Returns `None` when nothing is configured, which is the inert case rather\\n/// than a failure.\\n#[must_use]\\npub fn resolve_traces_url(config: &OtelConfig) -> Option<String> {\\n if let Some(explicit) = config.traces_endpoint.as_deref().filter(|s| !s.is_empty()) {\\n return Some(explicit.to_owned());\\n }\\n let base = config.endpoint.as_deref().filter(|s| !s.is_empty())?;\\n Some(format!(\\\"{}/v1/traces\\\", base.trim_end_matches('/')))\\n}\\n\\n/// Escape a string for a JSON document.\\n///\\n/// Hand-written rather than pulled from `serde_json`, for the same reason the\\n/// `.ts` sibling hand-builds its payload: this compiles into a WASM shared core\\n/// where every dependency is paid for by each consumer, and OTLP/HTTP JSON is a\\n/// small, fully documented shape. The escape set is the one JSON requires —\\n/// quote, backslash, the two-character forms, and every remaining control\\n/// character as `\\\\u00XX`.\\nfn escape_json(s: &str, out: &mut String) {\\n for c in s.chars() {\\n match c {\\n '\\\"' => out.push_str(\\\"\\\\\\\\\\\\\\\"\\\"),\\n '\\\\\\\\' => out.push_str(\\\"\\\\\\\\\\\\\\\\\\\"),\\n '\\\\n' => out.push_str(\\\"\\\\\\\\n\\\"),\\n '\\\\r' => out.push_str(\\\"\\\\\\\\r\\\"),\\n '\\\\t' => out.push_str(\\\"\\\\\\\\t\\\"),\\n '\\\\u{8}' => out.push_str(\\\"\\\\\\\\b\\\"),\\n '\\\\u{c}' => out.push_str(\\\"\\\\\\\\f\\\"),\\n c if c.is_control() => {\\n let _ = fmt::Write::write_fmt(out, format_args!(\\\"\\\\\\\\u{:04x}\\\", c as u32));\\n }\\n c => out.push(c),\\n }\\n }\\n}\\n\\nfn write_string(value: &str, out: &mut String) {\\n out.push('\\\"');\\n escape_json(value, out);\\n out.push('\\\"');\\n}\\n\\n/// Serialise one attribute as an OTLP `KeyValue`.\\nfn write_attribute(key: &str, value: &AttributeValue, out: &mut String) {\\n out.push_str(\\\"{\\\\\\\"key\\\\\\\":\\\");\\n write_string(key, out);\\n out.push_str(\\\",\\\\\\\"value\\\\\\\":{\\\");\\n match value {\\n AttributeValue::Str(v) => {\\n out.push_str(\\\"\\\\\\\"stringValue\\\\\\\":\\\");\\n write_string(v, out);\\n }\\n // Quoted: JSON has no int64, so the OTLP JSON mapping specifies a string.\\n AttributeValue::Int(v) => {\\n let _ = fmt::Write::write_fmt(out, format_args!(\\\"\\\\\\\"intValue\\\\\\\":\\\\\\\"{v}\\\\\\\"\\\"));\\n }\\n AttributeValue::Double(v) => {\\n let _ = fmt::Write::write_fmt(out, format_args!(\\\"\\\\\\\"doubleValue\\\\\\\":{v}\\\"));\\n }\\n AttributeValue::Bool(v) => {\\n let _ = fmt::Write::write_fmt(out, format_args!(\\\"\\\\\\\"boolValue\\\\\\\":{v}\\\"));\\n }\\n }\\n out.push_str(\\\"}}\\\");\\n}\\n\\nfn write_attributes(attributes: &BTreeMap<String, AttributeValue>, out: &mut String) {\\n out.push('[');\\n for (i, (key, value)) in attributes.iter().enumerate() {\\n if i > 0 {\\n out.push(',');\\n }\\n write_attribute(key, value, out);\\n }\\n out.push(']');\\n}\\n\\n/// Build the OTLP/HTTP JSON body for a set of spans.\\n///\\n/// Exported separately from [`build_trace_request`] so a caller can inspect,\\n/// queue or persist the payload without committing to sending it.\\n#[must_use]\\npub fn build_trace_body(spans: &[Span], config: &OtelConfig, trace_id: TraceId) -> String {\\n let mut resource: BTreeMap<String, AttributeValue> = BTreeMap::new();\\n resource.insert(\\n \\\"service.name\\\".to_owned(),\\n AttributeValue::Str(config.service_name.clone()),\\n );\\n if let Some(v) = &config.service_version {\\n resource.insert(\\\"service.version\\\".to_owned(), AttributeValue::Str(v.clone()));\\n }\\n if let Some(v) = &config.environment {\\n resource.insert(\\n \\\"deployment.environment.name\\\".to_owned(),\\n AttributeValue::Str(v.clone()),\\n );\\n }\\n resource.insert(\\n \\\"telemetry.sdk.name\\\".to_owned(),\\n AttributeValue::Str(\\\"mzizi-otel\\\".to_owned()),\\n );\\n resource.insert(\\n \\\"telemetry.sdk.language\\\".to_owned(),\\n AttributeValue::Str(\\\"rust\\\".to_owned()),\\n );\\n\\n let mut out = String::with_capacity(512 + spans.len() * 256);\\n out.push_str(\\\"{\\\\\\\"resourceSpans\\\\\\\":[{\\\\\\\"resource\\\\\\\":{\\\\\\\"attributes\\\\\\\":\\\");\\n write_attributes(&resource, &mut out);\\n out.push_str(\\\"},\\\\\\\"scopeSpans\\\\\\\":[{\\\\\\\"scope\\\\\\\":{\\\\\\\"name\\\\\\\":\\\\\\\"mzizi.n8.assurance\\\\\\\"},\\\\\\\"spans\\\\\\\":[\\\");\\n\\n for (i, span) in spans.iter().enumerate() {\\n if i > 0 {\\n out.push(',');\\n }\\n out.push_str(\\\"{\\\\\\\"traceId\\\\\\\":\\\");\\n write_string(&trace_id.to_string(), &mut out);\\n out.push_str(\\\",\\\\\\\"spanId\\\\\\\":\\\");\\n write_string(&span.span_id.to_string(), &mut out);\\n if let Some(parent) = span.parent_span_id {\\n out.push_str(\\\",\\\\\\\"parentSpanId\\\\\\\":\\\");\\n write_string(&parent.to_string(), &mut out);\\n }\\n out.push_str(\\\",\\\\\\\"name\\\\\\\":\\\");\\n write_string(&span.name, &mut out);\\n let _ = fmt::Write::write_fmt(\\n &mut out,\\n format_args!(\\n \\\",\\\\\\\"kind\\\\\\\":{},\\\\\\\"startTimeUnixNano\\\\\\\":\\\\\\\"{}\\\\\\\",\\\\\\\"endTimeUnixNano\\\\\\\":\\\\\\\"{}\\\\\\\"\\\",\\n span.kind as u8,\\n to_unix_nano(span.start_time_ms),\\n to_unix_nano(span.end_time_ms),\\n ),\\n );\\n out.push_str(\\\",\\\\\\\"attributes\\\\\\\":\\\");\\n write_attributes(&span.attributes, &mut out);\\n let _ = fmt::Write::write_fmt(\\n &mut out,\\n format_args!(\\\",\\\\\\\"status\\\\\\\":{{\\\\\\\"code\\\\\\\":{}\\\", span.status as u8),\\n );\\n if let Some(message) = &span.status_message {\\n out.push_str(\\\",\\\\\\\"message\\\\\\\":\\\");\\n write_string(message, &mut out);\\n }\\n out.push_str(\\\"}}\\\");\\n }\\n\\n out.push_str(\\\"]}]}]}\\\");\\n out\\n}\\n\\n/// Build the request a host should POST, or say why there is nothing to send.\\n///\\n/// # Errors\\n///\\n/// Returns [`NotExported`] when there are no spans, or when no endpoint is\\n/// configured. Neither is a malfunction — see that type's documentation.\\npub fn build_trace_request(\\n spans: &[Span],\\n config: &OtelConfig,\\n trace_id: TraceId,\\n) -> Result<TraceRequest, NotExported> {\\n if spans.is_empty() {\\n return Err(NotExported::NoSpans);\\n }\\n let url = resolve_traces_url(config).ok_or(NotExported::NoEndpoint)?;\\n\\n let mut headers = BTreeMap::new();\\n headers.insert(\\\"Content-Type\\\".to_owned(), \\\"application/json\\\".to_owned());\\n for (k, v) in &config.headers {\\n headers.insert(k.clone(), v.clone());\\n }\\n\\n Ok(TraceRequest {\\n url,\\n headers,\\n body: build_trace_body(spans, config, trace_id),\\n })\\n}\\n\\n// ── the N8 probe bridge ────────────────────────────────────────────────────\\n\\n// The probe types are NOT redefined here. `mzizi-synthetic-probe` owns them, and\\n// this module borrows them exactly as the `.ts` sibling does with its\\n// `import type { ProbeResult } from \\\"./mzizi-synthetic-probe\\\"` and its declared\\n// `registryDependencies` entry.\\n//\\n// The first draft of this file defined its own, which duplicated the contract and\\n// got it subtly wrong within one sitting: the `.ts` has TWO step shapes — the\\n// input `ProbeStep` a runner executes, and an anonymous outcome shape inside\\n// `ProbeResult` — and the copy here collapsed them into one. Two definitions of a\\n// wire contract drift; one does not.\\n//\\n// Rust has no type-only import, so this is a real intra-crate dependency rather\\n// than an erased one. That is fine and is the distribution model: a Rust consumer\\n// takes the crate, where the `.ts` consumer takes two installed files.\\nuse super::mzizi_synthetic_probe::{ProbeResult, StepStatus};\\n\\n/// Turn a [`ProbeResult`] into a run span plus one child span per step.\\n///\\n/// Span ids are supplied rather than generated, for the reason in the module\\n/// documentation: `ids[0]` is the run, `ids[1..]` the steps. Fewer ids than\\n/// steps truncates rather than inventing one, because a duplicated span id\\n/// corrupts the trace a collector assembles.\\n///\\n/// `ProbeResult` carries a duration per step but no per-step timestamp, so steps\\n/// are laid end to end from the run start. The durations are measured; the\\n/// offsets are reconstructed, and saying so here is the difference between an\\n/// approximation and a false claim.\\n#[must_use]\\npub fn probe_result_to_spans(\\n result: &ProbeResult,\\n ids: &[SpanId],\\n extra: &BTreeMap<String, AttributeValue>,\\n) -> Vec<Span> {\\n let Some((&run_id, step_ids)) = ids.split_first() else {\\n return Vec::new();\\n };\\n\\n let failed_steps = result\\n .steps\\n .iter()\\n .filter(|s| s.status == StepStatus::Fail)\\n .count();\\n\\n let mut run_attrs: BTreeMap<String, AttributeValue> = BTreeMap::new();\\n run_attrs.insert(\\\"mzizi.node\\\".to_owned(), AttributeValue::Int(8));\\n run_attrs.insert(\\n \\\"mzizi.probe.journey\\\".to_owned(),\\n AttributeValue::Str(result.journey_id.clone()),\\n );\\n run_attrs.insert(\\n \\\"mzizi.probe.status\\\".to_owned(),\\n AttributeValue::Str(result.status.as_str().to_owned()),\\n );\\n run_attrs.insert(\\n \\\"mzizi.probe.region\\\".to_owned(),\\n AttributeValue::Str(result.region.clone()),\\n );\\n run_attrs.insert(\\n \\\"mzizi.probe.steps\\\".to_owned(),\\n AttributeValue::Int(result.steps.len() as i64),\\n );\\n run_attrs.insert(\\n \\\"mzizi.probe.steps_failed\\\".to_owned(),\\n AttributeValue::Int(failed_steps as i64),\\n );\\n for (k, v) in extra {\\n run_attrs.insert(k.clone(), v.clone());\\n }\\n\\n let failed = result.status.is_failure();\\n let mut spans = Vec::with_capacity(1 + result.steps.len());\\n spans.push(Span {\\n name: format!(\\\"probe {}\\\", result.journey_id),\\n kind: SpanKind::Internal,\\n span_id: run_id,\\n parent_span_id: None,\\n start_time_ms: result.started_at_ms,\\n end_time_ms: result.started_at_ms + result.duration_ms,\\n attributes: run_attrs,\\n status: if failed {\\n StatusCode::Error\\n } else {\\n StatusCode::Ok\\n },\\n status_message: failed\\n .then(|| format!(\\\"probe {} {}\\\", result.journey_id, result.status.as_str())),\\n });\\n\\n let mut cursor = result.started_at_ms;\\n for (step, &span_id) in result.steps.iter().zip(step_ids) {\\n let start = cursor;\\n cursor += step.duration_ms;\\n\\n let mut attrs: BTreeMap<String, AttributeValue> = BTreeMap::new();\\n attrs.insert(\\\"mzizi.node\\\".to_owned(), AttributeValue::Int(8));\\n attrs.insert(\\n \\\"mzizi.probe.journey\\\".to_owned(),\\n AttributeValue::Str(result.journey_id.clone()),\\n );\\n attrs.insert(\\n \\\"mzizi.probe.step_status\\\".to_owned(),\\n AttributeValue::Str(\\n match step.status {\\n StepStatus::Pass => \\\"pass\\\",\\n StepStatus::Fail => \\\"fail\\\",\\n }\\n .to_owned(),\\n ),\\n );\\n if let Some(err) = &step.error {\\n attrs.insert(\\\"error.message\\\".to_owned(), AttributeValue::Str(err.clone()));\\n }\\n\\n let step_failed = step.status == StepStatus::Fail;\\n spans.push(Span {\\n name: step.description.clone(),\\n kind: SpanKind::Client,\\n span_id,\\n parent_span_id: Some(run_id),\\n start_time_ms: start,\\n end_time_ms: cursor,\\n attributes: attrs,\\n status: if step_failed {\\n StatusCode::Error\\n } else {\\n StatusCode::Ok\\n },\\n status_message: step_failed.then(|| {\\n step.error\\n .clone()\\n .unwrap_or_else(|| \\\"step failed\\\".to_owned())\\n }),\\n });\\n }\\n\\n spans\\n}\\n\"}],\"contract\":null}",
|
|
1226
|
+
"mzizi-perf-probe": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-perf-probe\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"Web Vitals and performance monitoring. Tracks LCP, FID, CLS, INP, TTFB per page and per component (using data-portal backlinks). Reports to analytics. Competitors: Lighthouse, Web Vitals library, Vercel Analytics.\",\"crate\":{\"name\":\"mzizi-assurance\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n8-assurance/mzizi-perf-probe.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N8 assurance — Web Vitals rating and per-component performance.\\n//!\\n//! The Rust implementation of `mzizi-perf-probe`. The thresholds, the ratings, the\\n//! CLS fold and the report; not `PerformanceObserver`, not `performance.memory`,\\n//! not the sampling draw.\\n//!\\n//! # Four defects in the TypeScript, fixed here\\n//!\\n//! **1. An unknown metric was rated `good`.** `rateMetric` looks the name up in a\\n//! `Record<string, …>` and returns `\\\"good\\\"` when the lookup misses — so a typo, a\\n//! metric added ahead of its threshold, or anything the table does not know about\\n//! reports as healthy. This is the fail-open shape CLAUDE.md §14 forbids for\\n//! entitlement, and it is no better here: \\\"we have no threshold for this\\\" must not\\n//! resolve to \\\"this is fine\\\". [`Vital`] is a closed enum and [`rate`] matches it\\n//! totally, so the case cannot arise; [`Rating::Unrated`] exists for a host that\\n//! measured something this core has no threshold for.\\n//!\\n//! **2. Per-component metrics were three literal zeros.** `renderTimeMs: 0,\\n//! rerenderCount: 0, mountTimeMs: 0` for every component on the page — and\\n//! `usePerfMark` writes `performance.mark` entries that nothing ever reads, since\\n//! `performance.measure` is never called and the marks are never collected. So the\\n//! component half of the probe measures nothing and reports zeros as though it\\n//! had. A table showing every component rendering in 0ms is not an empty dashboard,\\n//! it is a dashboard saying everything is perfect. Every measurement here is an\\n//! [`Option`], and [`ComponentPerf::from_marks`] is the only thing that fills one.\\n//!\\n//! **3. `totalRenderTimeMs` was `performance.now()`** — milliseconds since the\\n//! page started, read inside a `setTimeout(…, 5000)`. So it was the age of the\\n//! page, never below 5000, and had nothing to do with rendering. Here it is the\\n//! sum of what was actually measured, and [`None`] when nothing was.\\n//!\\n//! **4. CLS was recorded once per observer batch.** `recordMetric(\\\"CLS\\\", clsValue)`\\n//! fires inside the observer callback, so `vitals` accumulates one CLS entry per\\n//! batch, each carrying the running cumulative total, and `onMetric` fires\\n//! repeatedly for one metric. Anyone averaging that array gets a number that means\\n//! nothing. CLS is cumulative by definition — [`VitalsAccumulator`] folds it and\\n//! keeps ONE value.\\n//!\\n//! # Preserved\\n//!\\n//! `memoryUsageMB` stays optional and absent rather than zero. `performance.memory`\\n//! is Chromium-only, and the `.ts` had already been fixed to stop dividing\\n//! `undefined` by 1048576 and reporting **NaN** on every other browser — a\\n//! number-shaped value that poisons any average computed from it. Same rule, same\\n//! reason: absent is the honest answer.\\n\\nuse std::collections::BTreeMap;\\n\\n/// A Web Vital this core can rate.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum Vital {\\n /// Largest Contentful Paint, milliseconds.\\n Lcp,\\n /// First Input Delay, milliseconds.\\n Fid,\\n /// Cumulative Layout Shift, unitless.\\n Cls,\\n /// Interaction to Next Paint, milliseconds.\\n Inp,\\n /// Time To First Byte, milliseconds.\\n Ttfb,\\n /// First Contentful Paint, milliseconds.\\n Fcp,\\n}\\n\\nimpl Vital {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Lcp => \\\"LCP\\\",\\n Self::Fid => \\\"FID\\\",\\n Self::Cls => \\\"CLS\\\",\\n Self::Inp => \\\"INP\\\",\\n Self::Ttfb => \\\"TTFB\\\",\\n Self::Fcp => \\\"FCP\\\",\\n }\\n }\\n\\n /// Parse a wire spelling.\\n ///\\n /// Returns [`None`] for anything unknown, which the caller must handle — the\\n /// `.ts` resolves the same situation to a `good` rating. See defect 1.\\n ///\\n /// Deliberately not `FromStr`: that trait's `Err` would have to name a type,\\n /// and \\\"this is not a vital I have a threshold for\\\" is an absence rather than\\n /// an error.\\n #[must_use]\\n pub fn parse(value: &str) -> Option<Self> {\\n match value {\\n \\\"LCP\\\" => Some(Self::Lcp),\\n \\\"FID\\\" => Some(Self::Fid),\\n \\\"CLS\\\" => Some(Self::Cls),\\n \\\"INP\\\" => Some(Self::Inp),\\n \\\"TTFB\\\" => Some(Self::Ttfb),\\n \\\"FCP\\\" => Some(Self::Fcp),\\n _ => None,\\n }\\n }\\n\\n /// The `good` ceiling and the `poor` floor, from web.dev.\\n #[must_use]\\n pub const fn thresholds(self) -> (f64, f64) {\\n match self {\\n Self::Lcp => (2500.0, 4000.0),\\n Self::Fid => (100.0, 300.0),\\n Self::Cls => (0.1, 0.25),\\n Self::Inp => (200.0, 500.0),\\n Self::Ttfb => (800.0, 1800.0),\\n Self::Fcp => (1800.0, 3000.0),\\n }\\n }\\n\\n /// Whether this vital accumulates across the page's life.\\n ///\\n /// Only CLS does, and that is why it needs folding rather than appending.\\n #[must_use]\\n pub const fn is_cumulative(self) -> bool {\\n matches!(self, Self::Cls)\\n }\\n\\n /// Every vital this core rates.\\n #[must_use]\\n pub const fn all() -> [Self; 6] {\\n [\\n Self::Lcp,\\n Self::Fid,\\n Self::Cls,\\n Self::Inp,\\n Self::Ttfb,\\n Self::Fcp,\\n ]\\n }\\n}\\n\\n/// How a measurement scores against its thresholds.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum Rating {\\n /// Within the good ceiling.\\n Good,\\n /// Between the two.\\n NeedsImprovement,\\n /// At or beyond the poor floor.\\n Poor,\\n /// Measured, but this core has no threshold for it.\\n ///\\n /// Deliberately NOT `Good`. The `.ts` returns `\\\"good\\\"` on a threshold miss,\\n /// so an unrecognised metric reports as healthy.\\n Unrated,\\n}\\n\\nimpl Rating {\\n /// The wire spelling. `unrated` has no `.ts` counterpart, because the `.ts`\\n /// has no way to express it.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Good => \\\"good\\\",\\n Self::NeedsImprovement => \\\"needs-improvement\\\",\\n Self::Poor => \\\"poor\\\",\\n Self::Unrated => \\\"unrated\\\",\\n }\\n }\\n}\\n\\n/// Rate a measurement against its vital's thresholds.\\n///\\n/// The boundaries match the `.ts`: `<= good` is good, `>= poor` is poor, and the\\n/// band between them is `needs-improvement`.\\n#[must_use]\\npub fn rate(vital: Vital, value: f64) -> Rating {\\n let (good, poor) = vital.thresholds();\\n if value.is_nan() {\\n // A NaN compares false against everything, so every branch below would\\n // fall through to `needs-improvement` — a rating invented from a\\n // non-measurement.\\n return Rating::Unrated;\\n }\\n if value <= good {\\n Rating::Good\\n } else if value >= poor {\\n Rating::Poor\\n } else {\\n Rating::NeedsImprovement\\n }\\n}\\n\\n/// One rated measurement.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct VitalsMetric {\\n /// Which vital.\\n pub vital: Vital,\\n /// The measurement.\\n pub value: f64,\\n /// How it scores.\\n pub rating: Rating,\\n /// The PATH, never a full URL — a query string carries session tokens and\\n /// search terms, and this probe collects no PII.\\n pub url: String,\\n /// When, milliseconds since the Unix epoch.\\n pub at_ms: f64,\\n}\\n\\n/// The vitals seen so far on one page.\\n///\\n/// Cumulative vitals are FOLDED, not appended. The `.ts` pushes a fresh CLS entry\\n/// on every `layout-shift` batch, each holding the running total, so a page with\\n/// twelve shift batches reports twelve CLS metrics.\\n#[derive(Debug, Clone, Default)]\\npub struct VitalsAccumulator {\\n metrics: BTreeMap<Vital, VitalsMetric>,\\n}\\n\\nimpl VitalsAccumulator {\\n /// An empty accumulator.\\n #[must_use]\\n pub fn new() -> Self {\\n Self::default()\\n }\\n\\n /// Record a measurement, returning the value now held for that vital.\\n ///\\n /// A cumulative vital replaces its previous value — that value already\\n /// includes everything before it. A non-cumulative one also replaces, which\\n /// matches the browser: LCP is reported repeatedly and the last is the answer.\\n pub fn record(&mut self, vital: Vital, value: f64, url: &str, at_ms: f64) -> &VitalsMetric {\\n self.metrics.insert(\\n vital,\\n VitalsMetric {\\n vital,\\n value,\\n rating: rate(vital, value),\\n url: url.to_owned(),\\n at_ms,\\n },\\n );\\n &self.metrics[&vital]\\n }\\n\\n /// What is held for one vital.\\n #[must_use]\\n pub fn get(&self, vital: Vital) -> Option<&VitalsMetric> {\\n self.metrics.get(&vital)\\n }\\n\\n /// Every vital held, one entry each.\\n #[must_use]\\n pub fn metrics(&self) -> Vec<&VitalsMetric> {\\n self.metrics.values().collect()\\n }\\n\\n /// The worst rating present — the one-glance answer.\\n ///\\n /// [`Rating::Unrated`] does not win: it is an absence of judgement, not a bad\\n /// one, and letting it outrank `Poor` would hide a real failure behind an\\n /// unknown metric.\\n #[must_use]\\n pub fn worst_rating(&self) -> Option<Rating> {\\n self.metrics\\n .values()\\n .map(|m| m.rating)\\n .filter(|r| *r != Rating::Unrated)\\n .max()\\n }\\n}\\n\\n/// A `performance.mark` pair for one component.\\n#[derive(Debug, Clone, Copy, PartialEq)]\\npub struct MarkPair {\\n /// When the component mounted, milliseconds on the performance timeline.\\n pub mount_ms: f64,\\n /// When it unmounted, if it has.\\n pub unmount_ms: Option<f64>,\\n}\\n\\n/// What a component actually cost.\\n///\\n/// Every measurement is optional, because the honest answer to \\\"how long did this\\n/// take\\\" is frequently \\\"nobody measured\\\". The `.ts` answers `0`.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct ComponentPerf {\\n /// The `data-slot` value.\\n pub component_name: String,\\n /// Which helix node. Uncapped.\\n pub node: Option<u32>,\\n /// Where its documentation is.\\n pub portal_url: Option<String>,\\n /// How long it was mounted for.\\n pub mount_duration_ms: Option<f64>,\\n /// How long its render took.\\n pub render_time_ms: Option<f64>,\\n /// How many times it re-rendered.\\n pub rerender_count: Option<u32>,\\n}\\n\\nimpl ComponentPerf {\\n /// A component that was seen on the page but never measured.\\n ///\\n /// This is what the `.ts` produces for every component; the difference is that\\n /// here it says so rather than reporting three zeros.\\n #[must_use]\\n pub fn observed(component_name: impl Into<String>) -> Self {\\n Self {\\n component_name: component_name.into(),\\n node: None,\\n portal_url: None,\\n mount_duration_ms: None,\\n render_time_ms: None,\\n rerender_count: None,\\n }\\n }\\n\\n /// Fill in what a mark pair says.\\n ///\\n /// An unmount mark that precedes its mount is discarded rather than reported\\n /// as a negative duration: clocks and mark ordering are the host's business,\\n /// and a negative lifetime is a measurement error, not a fast component.\\n #[must_use]\\n pub fn from_marks(component_name: impl Into<String>, marks: MarkPair) -> Self {\\n let mut perf = Self::observed(component_name);\\n perf.mount_duration_ms = marks\\n .unmount_ms\\n .map(|end| end - marks.mount_ms)\\n .filter(|d| *d >= 0.0);\\n perf\\n }\\n\\n /// Whether anything at all was measured.\\n #[must_use]\\n pub fn is_measured(&self) -> bool {\\n self.mount_duration_ms.is_some()\\n || self.render_time_ms.is_some()\\n || self.rerender_count.is_some()\\n }\\n}\\n\\n/// One page's performance.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct PerfReport {\\n /// The path measured.\\n pub url: String,\\n /// When, milliseconds since the Unix epoch.\\n pub at_ms: f64,\\n /// One entry per vital.\\n pub vitals: Vec<VitalsMetric>,\\n /// One entry per component seen.\\n pub components: Vec<ComponentPerf>,\\n /// The sum of the render times that were MEASURED.\\n ///\\n /// [`None`] when none were — which the `.ts` answers with `performance.now()`,\\n /// the age of the page, read inside a five-second timeout.\\n pub total_render_time_ms: Option<f64>,\\n /// Heap in megabytes, when the runtime reports it. Chromium only.\\n pub memory_usage_mb: Option<f64>,\\n}\\n\\n/// Assemble a report from what was gathered.\\n#[must_use]\\npub fn build_report(\\n url: impl Into<String>,\\n at_ms: f64,\\n vitals: &VitalsAccumulator,\\n components: Vec<ComponentPerf>,\\n memory_usage_mb: Option<f64>,\\n) -> PerfReport {\\n let measured: Vec<f64> = components.iter().filter_map(|c| c.render_time_ms).collect();\\n PerfReport {\\n url: url.into(),\\n at_ms,\\n vitals: vitals.metrics().into_iter().cloned().collect(),\\n total_render_time_ms: (!measured.is_empty()).then(|| measured.iter().sum()),\\n components,\\n memory_usage_mb,\\n }\\n}\\n\\n/// The default sample rate, matching the `.ts`.\\npub const DEFAULT_SAMPLE_RATE: f64 = 0.1;\\n\\n/// Whether this pageload is sampled.\\n///\\n/// `draw` is a value in `[0, 1)` supplied by the host, for the same reason as in\\n/// `mzizi-rum`: a core with no I/O has no entropy source, and a supplied draw is\\n/// testable at both boundaries.\\n#[must_use]\\npub fn should_sample(draw: f64, sample_rate: f64) -> bool {\\n draw < sample_rate\\n}\\n\"}],\"contract\":null}",
|
|
1227
|
+
"mzizi-persistent-player": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-persistent-player\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Media player that persists across navigation. Mini-player bar at bottom when navigating away from media. Supports video, audio, and live streams. Competitors: YouTube mini-player, Spotify persistent bar, Netflix PiP.\",\"crate\":{\"name\":\"mzizi-shell\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n7-shell/mzizi-persistent-player.rs\",\"type\":\"registry:rust\",\"content\":\"//! NYUCHI PERSISTENT PLAYER — N7 shell, Dioxus.\\n//!\\n//! The Rust sibling of `mzizi-persistent-player.tsx`: a mini-player pinned above the bottom\\n//! nav, sharing its contract — same `data-slot`, same play/pause/close controls.\\n//!\\n//! # Progress is clamped, unlike the `.tsx`\\n//!\\n//! The `.tsx` interpolates `${progress}%` into the bar's CSS width with no bound check, so an\\n//! out-of-range value (e.g. a stale duration calculation putting `progress` above 100 or below\\n//! 0) renders verbatim and overflows or underflows the track. Clamped here to `[0, 100]`.\\n//!\\n//! **The `.tsx` sibling still has this.**\\n\\nuse dioxus::prelude::*;\\n\\n/// The kind of media a track represents.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\\npub enum MediaKind {\\n /// Audio-only playback.\\n Audio,\\n /// Audio + video playback.\\n Video,\\n /// A live stream.\\n Live,\\n}\\n\\n/// The track currently loaded in the player.\\n#[derive(Clone, Debug, PartialEq)]\\npub struct MediaTrack {\\n /// Unique identifier.\\n pub id: String,\\n /// Track title.\\n pub title: String,\\n /// Optional artist/creator name.\\n pub artist: Option<String>,\\n /// Optional thumbnail image URL.\\n pub thumbnail: Option<String>,\\n /// The kind of media this track is.\\n pub kind: MediaKind,\\n}\\n\\n/// Clamps a progress percentage to `[0, 100]` — see the module docs for why.\\npub fn clamp_progress(progress: f64) -> f64 {\\n progress.clamp(0.0, 100.0)\\n}\\n\\n/// Props for [`MziziPersistentPlayer`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziPersistentPlayerProps {\\n /// The currently loaded track, or `None` to render nothing.\\n pub track: Option<MediaTrack>,\\n /// Whether playback is active.\\n #[props(default = false)]\\n pub is_playing: bool,\\n /// Playback progress as a percentage.\\n #[props(default = 0.0)]\\n pub progress: f64,\\n /// Fired when play is requested.\\n #[props(default)]\\n pub on_play: EventHandler<()>,\\n /// Fired when pause is requested.\\n #[props(default)]\\n pub on_pause: EventHandler<()>,\\n /// Fired when the player should expand to full view.\\n #[props(default)]\\n pub on_expand: EventHandler<()>,\\n /// Fired when the player is closed.\\n #[props(default)]\\n pub on_close: EventHandler<()>,\\n}\\n\\n/// Mini-player pinned above the bottom nav, with play/pause and progress.\\n#[component]\\npub fn MziziPersistentPlayer(props: MziziPersistentPlayerProps) -> Element {\\n let Some(track) = props.track.clone() else {\\n return rsx! {};\\n };\\n let progress = clamp_progress(props.progress);\\n\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-persistent-player\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-persistent-player\\\",\\n role: \\\"region\\\",\\n \\\"aria-label\\\": \\\"Now playing: {track.title}\\\",\\n class: \\\"safe-area-bottom fixed right-0 bottom-16 left-0 z-30 border-t border-border bg-card/95 backdrop-blur-sm\\\",\\n\\n div { class: \\\"h-0.5 bg-muted\\\",\\n div { class: \\\"h-full bg-primary transition-all\\\", style: \\\"width: {progress}%\\\" }\\n }\\n\\n div { class: \\\"flex items-center gap-3 px-4 py-2\\\",\\n button {\\n class: \\\"min-w-0 flex-1 text-left\\\",\\n onclick: move |_| props.on_expand.call(()),\\n p { class: \\\"truncate text-sm font-medium\\\", \\\"{track.title}\\\" }\\n if let Some(artist) = &track.artist {\\n p { class: \\\"truncate text-xs text-muted-foreground\\\", \\\"{artist}\\\" }\\n }\\n }\\n div { class: \\\"flex shrink-0 items-center gap-1\\\",\\n button {\\n \\\"aria-label\\\": if props.is_playing { \\\"Pause\\\" } else { \\\"Play\\\" },\\n class: \\\"flex size-10 min-h-[48px] min-w-[48px] items-center justify-center rounded-full transition-colors hover:bg-muted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\\\",\\n onclick: move |_| if props.is_playing { props.on_pause.call(()) } else { props.on_play.call(()) },\\n if props.is_playing { \\\"\\\\u{23F8}\\\" } else { \\\"\\\\u{25B6}\\\" }\\n }\\n button {\\n \\\"aria-label\\\": \\\"Close player\\\",\\n class: \\\"flex size-10 min-h-[48px] min-w-[48px] items-center justify-center rounded-full transition-colors hover:bg-muted\\\",\\n onclick: move |_| props.on_close.call(()),\\n \\\"\\\\u{2715}\\\"\\n }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn progress_within_range_is_unchanged() {\\n assert_eq!(clamp_progress(42.0), 42.0);\\n }\\n\\n #[test]\\n fn progress_above_100_is_clamped() {\\n // The .tsx has no such guard: `style={{ width: `${progress}%` }}`\\n // would render `width: 137%` verbatim, overflowing the track.\\n assert_eq!(clamp_progress(137.0), 100.0);\\n }\\n\\n #[test]\\n fn negative_progress_is_clamped_to_zero() {\\n assert_eq!(clamp_progress(-5.0), 0.0);\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1228
|
+
"mzizi-platform-health": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-platform-health\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Transparent platform health dashboard — N8 Assurance (Z-axis/depth). Observes the full 3D architecture: every horizontal layer (L2-L7), every vertical spine (L1 tokens, L4 safety, L5 resilience), and reports which intersections are operational, degraded, or in outage. The depth view into the system.\",\"crate\":{\"name\":\"mzizi-assurance\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n8-assurance/mzizi-platform-health.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N8 assurance — what a set of service statuses adds up to.\\n//!\\n//! The Rust core behind `mzizi-platform-health`. The component is a UI surface and\\n//! gets a Dioxus alternative rather than an implementation (CLAUDE.md §6.2); what\\n//! lives here is the part that is not UI at all — the rollup from a list of\\n//! services to the single line at the top of the panel, and the status → label\\n//! mapping that has to agree across every target.\\n//!\\n//! Putting it here is what makes that agreement possible. In the `.tsx` the rollup\\n//! is three chained ternaries inside the render, so a Swift panel and a Dioxus\\n//! panel would each re-derive it, and the three would drift silently — the failure\\n//! being that two dashboards showing the same services disagree about whether\\n//! anything is wrong.\\n//!\\n//! # Three defects in the TypeScript, fixed here\\n//!\\n//! **1. An empty service list reported \\\"All Systems Operational\\\", in green.**\\n//! `services.every(…)` is `true` for an empty array, so a panel that fetched\\n//! nothing — the request failed, the list has not loaded, the config is wrong —\\n//! renders exactly like a healthy platform. This is the worst possible failure for\\n//! a status page: it is the one surface whose entire job is to be trusted at a\\n//! glance, and knowing nothing renders as knowing everything is fine.\\n//! [`Overall::Unknown`] is what nothing adds up to.\\n//!\\n//! **2. Planned maintenance was reported as \\\"Issues Detected\\\".** `allOk` is false\\n//! for any non-`operational` status, and the fallback branch is `degraded` — so a\\n//! service someone deliberately took down on a schedule turns the header amber and\\n//! announces a problem. Maintenance is a state the operator chose; a status page\\n//! that cries about it is one people stop reading.\\n//!\\n//! **3. `unknown` was folded into `degraded` too.** Same fallback branch. \\\"We\\n//! could not determine this service's state\\\" and \\\"this service is impaired\\\" are\\n//! different claims, and a status page that cannot tell them apart cannot be used\\n//! to decide anything. [`Overall::Unknown`] carries it separately.\\n//!\\n//! # Colours are not here, deliberately\\n//!\\n//! `STATUS_CONFIG` in the `.tsx` pairs each status with `var(--health-operational,\\n//! #22C55E)` and friends. The variable reference is right and the hex fallback is\\n//! a design value defined outside N1, which §7.4 forbids — but the fix is a token,\\n//! not a Rust constant. Duplicating the hex here would put the same wrong value in\\n//! a second place. Each target reads `--health-<status>`; this core answers which\\n//! status applies.\\n\\n/// How one service is doing.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum ServiceStatus {\\n /// Working.\\n Operational,\\n /// Taken down on purpose.\\n Maintenance,\\n /// State could not be determined.\\n Unknown,\\n /// Working badly.\\n Degraded,\\n /// Not working.\\n Outage,\\n}\\n\\nimpl ServiceStatus {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Operational => \\\"operational\\\",\\n Self::Degraded => \\\"degraded\\\",\\n Self::Outage => \\\"outage\\\",\\n Self::Maintenance => \\\"maintenance\\\",\\n Self::Unknown => \\\"unknown\\\",\\n }\\n }\\n\\n /// The human label, matching the `.tsx`'s `STATUS_CONFIG`.\\n #[must_use]\\n pub const fn label(self) -> &'static str {\\n match self {\\n Self::Operational => \\\"Operational\\\",\\n Self::Degraded => \\\"Degraded\\\",\\n Self::Outage => \\\"Outage\\\",\\n Self::Maintenance => \\\"Maintenance\\\",\\n Self::Unknown => \\\"Unknown\\\",\\n }\\n }\\n\\n /// The CSS custom property a target reads for this status's colour.\\n ///\\n /// The property, not the value. See the module docs.\\n #[must_use]\\n pub const fn color_var(self) -> &'static str {\\n match self {\\n Self::Operational => \\\"--health-operational\\\",\\n Self::Degraded => \\\"--health-degraded\\\",\\n Self::Outage => \\\"--health-outage\\\",\\n Self::Maintenance => \\\"--health-maintenance\\\",\\n Self::Unknown => \\\"--color-muted-foreground\\\",\\n }\\n }\\n\\n /// Whether this status means something is wrong.\\n ///\\n /// `maintenance` is NOT a problem — it is a state an operator chose — and\\n /// `unknown` is not a problem either, it is an absence of information.\\n #[must_use]\\n pub const fn is_problem(self) -> bool {\\n matches!(self, Self::Degraded | Self::Outage)\\n }\\n}\\n\\n/// One service on the panel.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct ServiceHealth {\\n /// What it is called.\\n pub name: String,\\n /// How it is doing.\\n pub status: ServiceStatus,\\n /// How fast it answered, when that was measured.\\n pub latency_ms: Option<f64>,\\n /// Anything worth saying about it.\\n pub message: Option<String>,\\n}\\n\\n/// What the whole panel says in one line.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum Overall {\\n /// Everything is working.\\n Operational,\\n /// Nothing is broken, but something is deliberately down.\\n UnderMaintenance,\\n /// Something is impaired.\\n Degraded,\\n /// Something is down.\\n Outage,\\n /// Nothing is known — no services were reported.\\n ///\\n /// The `.tsx` has no such case: `[].every(…)` is `true`, so an empty list\\n /// renders green and says \\\"All Systems Operational\\\".\\n Unknown,\\n}\\n\\nimpl Overall {\\n /// The headline.\\n #[must_use]\\n pub const fn headline(self) -> &'static str {\\n match self {\\n Self::Operational => \\\"All Systems Operational\\\",\\n Self::UnderMaintenance => \\\"Planned Maintenance\\\",\\n Self::Degraded | Self::Outage => \\\"Issues Detected\\\",\\n Self::Unknown => \\\"Status Unavailable\\\",\\n }\\n }\\n\\n /// The CSS custom property the header dot and text read.\\n #[must_use]\\n pub const fn color_var(self) -> &'static str {\\n match self {\\n Self::Operational => ServiceStatus::Operational.color_var(),\\n Self::UnderMaintenance => ServiceStatus::Maintenance.color_var(),\\n Self::Degraded => ServiceStatus::Degraded.color_var(),\\n Self::Outage => ServiceStatus::Outage.color_var(),\\n Self::Unknown => ServiceStatus::Unknown.color_var(),\\n }\\n }\\n\\n /// Whether this warrants somebody looking.\\n #[must_use]\\n pub const fn needs_attention(self) -> bool {\\n matches!(self, Self::Degraded | Self::Outage)\\n }\\n}\\n\\n/// Roll a list of services up into the one line at the top of the panel.\\n///\\n/// Worst wins, with the bands ordered outage → degraded → unknown → maintenance →\\n/// operational. `unknown` outranks `maintenance` because an unmeasured service\\n/// might be down, and a scheduled one is known not to be.\\n#[must_use]\\npub fn overall(services: &[ServiceHealth]) -> Overall {\\n if services.is_empty() {\\n // Nothing measured is not the same as nothing wrong. See defect 1: this\\n // is the case that made a broken fetch look like a healthy platform.\\n return Overall::Unknown;\\n }\\n if services.iter().any(|s| s.status == ServiceStatus::Outage) {\\n return Overall::Outage;\\n }\\n if services.iter().any(|s| s.status == ServiceStatus::Degraded) {\\n return Overall::Degraded;\\n }\\n if services.iter().any(|s| s.status == ServiceStatus::Unknown) {\\n return Overall::Unknown;\\n }\\n if services\\n .iter()\\n .any(|s| s.status == ServiceStatus::Maintenance)\\n {\\n return Overall::UnderMaintenance;\\n }\\n Overall::Operational\\n}\\n\\n/// How many services are in each state, for a summary line.\\n#[must_use]\\npub fn tally(services: &[ServiceHealth], status: ServiceStatus) -> usize {\\n services.iter().filter(|s| s.status == status).count()\\n}\\n\"}],\"contract\":null}",
|
|
1229
|
+
"mzizi-route-guard": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-route-guard\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Route-level protection gate. Checks auth state, role, subscription tier, and verification level before rendering a route. Redirects to appropriate screen (login, upgrade, verification) if requirements not met. Composes L4 safety gates at the route level.\",\"crate\":{\"name\":\"mzizi-shell\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n7-shell/mzizi-route-guard.rs\",\"type\":\"registry:rust\",\"content\":\"//! NYUCHI ROUTE GUARD — N7 shell.\\n//!\\n//! The Rust sibling of `mzizi-route-guard.tsx`'s gating logic: route-level protection composed\\n//! of auth/role/subscription/verification checks.\\n//!\\n//! # The `.tsx`'s gates fail open, not closed\\n//!\\n//! Each optional check in the `.tsx` is guarded by `&&` against the corresponding user\\n//! attribute: `if (pass && config.roles?.length && userRole)`, and the same shape for\\n//! subscription and verification. When a route REQUIRES one of these but the user's value is\\n//! `undefined` — not yet loaded, or genuinely absent — the whole check is skipped and `pass`\\n//! stays `true`. A route gated on `subscription: \\\"premium\\\"` lets through a user whose tier\\n//! hasn't loaded yet. [`evaluate`] fails closed instead: a required attribute that is missing\\n//! denies access, the same as one that doesn't meet the bar.\\n//!\\n//! **The `.tsx` sibling still has this.**\\n//!\\n//! This file models only the gate itself — the pure decision behind the `.tsx`'s `check()`\\n//! effect. The Dioxus rendering shell around it (loading spinner / unauthorized fallback /\\n//! children) is a thin wrapper with nothing else worth testing.\\n\\n/// What level of authentication a route requires.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\\npub enum AuthRequirement {\\n /// No authentication required.\\n None,\\n /// Any signed-in user.\\n Authenticated,\\n /// A verified account.\\n Verified,\\n /// An admin account.\\n Admin,\\n}\\n\\n/// A route's access requirements.\\n#[derive(Clone, Debug, Default)]\\npub struct RouteGuardConfig {\\n /// Authentication level required, if any.\\n pub auth: Option<AuthRequirement>,\\n /// Roles allowed to access the route; empty means no role restriction.\\n pub roles: Vec<String>,\\n /// Minimum subscription tier required, if any.\\n pub subscription: Option<String>,\\n /// Minimum verification tier required, if any.\\n pub verification_tier: Option<String>,\\n}\\n\\n/// The current user's state as known by the host.\\n#[derive(Clone, Debug, Default)]\\npub struct UserState {\\n /// Whether the user is signed in.\\n pub is_authenticated: bool,\\n /// The user's role, if known.\\n pub role: Option<String>,\\n /// The user's subscription tier, if known.\\n pub tier: Option<String>,\\n /// The user's verification tier, if known.\\n pub verification: Option<String>,\\n}\\n\\nfn tier_rank(tiers: &[&str], value: &str) -> Option<usize> {\\n tiers.iter().position(|t| *t == value)\\n}\\n\\nconst SUBSCRIPTION_TIERS: [&str; 4] = [\\\"free\\\", \\\"premium\\\", \\\"business\\\", \\\"enterprise\\\"];\\nconst VERIFICATION_TIERS: [&str; 5] = [\\\"unverified\\\", \\\"community\\\", \\\"otp\\\", \\\"government\\\", \\\"licensed\\\"];\\n\\n/// Evaluates the gate. Returns `true` (allowed) or `false` (denied) — see the module docs for\\n/// how this differs from the `.tsx`'s fail-open behaviour.\\npub fn evaluate(config: &RouteGuardConfig, user: &UserState) -> bool {\\n if let Some(auth) = config.auth {\\n if auth != AuthRequirement::None && !user.is_authenticated {\\n return false;\\n }\\n }\\n\\n if !config.roles.is_empty() {\\n match &user.role {\\n Some(role) if config.roles.iter().any(|r| r == role) => {}\\n _ => return false,\\n }\\n }\\n\\n if let Some(required) = &config.subscription {\\n let required_rank = tier_rank(&SUBSCRIPTION_TIERS, required);\\n let user_rank = user\\n .tier\\n .as_deref()\\n .and_then(|t| tier_rank(&SUBSCRIPTION_TIERS, t));\\n match (user_rank, required_rank) {\\n (Some(u), Some(r)) if u >= r => {}\\n _ => return false,\\n }\\n }\\n\\n if let Some(required) = &config.verification_tier {\\n let required_rank = tier_rank(&VERIFICATION_TIERS, required);\\n let user_rank = user\\n .verification\\n .as_deref()\\n .and_then(|t| tier_rank(&VERIFICATION_TIERS, t));\\n match (user_rank, required_rank) {\\n (Some(u), Some(r)) if u >= r => {}\\n _ => return false,\\n }\\n }\\n\\n true\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n fn base_user() -> UserState {\\n UserState {\\n is_authenticated: true,\\n role: None,\\n tier: None,\\n verification: None,\\n }\\n }\\n\\n #[test]\\n fn unauthenticated_is_denied_when_auth_required() {\\n let config = RouteGuardConfig {\\n auth: Some(AuthRequirement::Authenticated),\\n ..Default::default()\\n };\\n let user = UserState {\\n is_authenticated: false,\\n ..base_user()\\n };\\n assert!(!evaluate(&config, &user));\\n }\\n\\n #[test]\\n fn missing_role_is_denied_not_silently_passed() {\\n // The .tsx: `if (pass && config.roles?.length && userRole)` — a\\n // route requiring roles but a user with no role at all (not yet\\n // loaded) skips the check and passes. Fixed here to deny.\\n let config = RouteGuardConfig {\\n roles: vec![\\\"admin\\\".into()],\\n ..Default::default()\\n };\\n let user = base_user();\\n assert!(!evaluate(&config, &user));\\n }\\n\\n #[test]\\n fn matching_role_is_allowed() {\\n let config = RouteGuardConfig {\\n roles: vec![\\\"admin\\\".into()],\\n ..Default::default()\\n };\\n let user = UserState {\\n role: Some(\\\"admin\\\".into()),\\n ..base_user()\\n };\\n assert!(evaluate(&config, &user));\\n }\\n\\n #[test]\\n fn missing_subscription_tier_is_denied_not_silently_passed() {\\n let config = RouteGuardConfig {\\n subscription: Some(\\\"premium\\\".into()),\\n ..Default::default()\\n };\\n let user = base_user();\\n assert!(!evaluate(&config, &user));\\n }\\n\\n #[test]\\n fn lower_subscription_tier_is_denied() {\\n let config = RouteGuardConfig {\\n subscription: Some(\\\"business\\\".into()),\\n ..Default::default()\\n };\\n let user = UserState {\\n tier: Some(\\\"free\\\".into()),\\n ..base_user()\\n };\\n assert!(!evaluate(&config, &user));\\n }\\n\\n #[test]\\n fn equal_or_higher_subscription_tier_is_allowed() {\\n let config = RouteGuardConfig {\\n subscription: Some(\\\"premium\\\".into()),\\n ..Default::default()\\n };\\n let user = UserState {\\n tier: Some(\\\"enterprise\\\".into()),\\n ..base_user()\\n };\\n assert!(evaluate(&config, &user));\\n }\\n\\n #[test]\\n fn missing_verification_tier_is_denied_not_silently_passed() {\\n let config = RouteGuardConfig {\\n verification_tier: Some(\\\"government\\\".into()),\\n ..Default::default()\\n };\\n let user = base_user();\\n assert!(!evaluate(&config, &user));\\n }\\n\\n #[test]\\n fn no_requirements_allows_any_authenticated_user() {\\n let config = RouteGuardConfig::default();\\n assert!(evaluate(&config, &base_user()));\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1230
|
+
"mzizi-rum": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-rum\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"Real User Monitoring. Collects page load timing, interaction delays, network quality, and navigation patterns from actual users. Aggregates by mini-app, page, device, and connection type. Privacy-respecting — no PII, only performance metrics. Competitors: Datadog RUM, Vercel Analytics.\",\"crate\":{\"name\":\"mzizi-assurance\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n8-assurance/mzizi-rum.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N8 assurance — real user monitoring: sampling, classification, batching.\\n//!\\n//! The Rust implementation of `mzizi-rum`. Privacy-first: no PII, and nothing\\n//! here can reach for any, because the core never touches a browser.\\n//!\\n//! # What this owns\\n//!\\n//! Whether to sample, what a viewport means, and how events accumulate into a\\n//! batch. Not `window`, not `performance`, not `navigator`, not the POST — the\\n//! host gathers the numbers and sends them.\\n//!\\n//! # Two defects in the TypeScript, fixed here\\n//!\\n//! **1. An unsampled collector still accumulated events, forever.** The `.ts`\\n//! constructor returns early when `Math.random() > sampleRate`, *before* calling\\n//! `init()` — so no flush timer is ever created. But `record()` stays public and\\n//! still pushes onto `this.events`, so any consumer calling it by hand on an\\n//! unsampled collector grows an array that nothing will ever drain. At the\\n//! default 10% sample rate that is nine sessions in ten.\\n//!\\n//! Here sampling is decided once, by [`should_sample`], and an unsampled\\n//! [`RumBuffer`] *discards* rather than silently hoarding.\\n//!\\n//! **2. The buffer had no ceiling even when sampled.** Events drain on a 30-second\\n//! timer, and a flush that fails is swallowed by design — correctly, since RUM\\n//! must never surface its own network error to the user it measures. Correct, and\\n//! unbounded: a collector whose endpoint is down accumulates for the life of the\\n//! session. [`RumBuffer`] caps and drops oldest, which loses the least recent\\n//! data rather than the whole tab.\\n//!\\n//! # Randomness is supplied, as everywhere in this node\\n//!\\n//! [`should_sample`] takes the random draw rather than making it. Not for the\\n//! security reason that applies to trace ids — sampling is not a correlation key\\n//! — but because a core with no I/O has no entropy source either, and a caller\\n//! that supplies the draw can write a deterministic test for the boundary.\\n\\n/// What kind of device, derived from the viewport.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum Device {\\n /// Narrow viewport.\\n Mobile,\\n /// Mid-width viewport.\\n Tablet,\\n /// Wide viewport.\\n Desktop,\\n}\\n\\nimpl Device {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Mobile => \\\"mobile\\\",\\n Self::Tablet => \\\"tablet\\\",\\n Self::Desktop => \\\"desktop\\\",\\n }\\n }\\n}\\n\\n/// Effective connection type, as the Network Information API reports it.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum Connection {\\n /// 4g or better.\\n FourG,\\n /// 3g.\\n ThreeG,\\n /// 2g.\\n TwoG,\\n /// Slower than 2g.\\n SlowTwoG,\\n /// Reported as wifi.\\n Wifi,\\n /// Not reported. Safari does not implement the API at all.\\n Unknown,\\n}\\n\\nimpl Connection {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::FourG => \\\"4g\\\",\\n Self::ThreeG => \\\"3g\\\",\\n Self::TwoG => \\\"2g\\\",\\n Self::SlowTwoG => \\\"slow-2g\\\",\\n Self::Wifi => \\\"wifi\\\",\\n Self::Unknown => \\\"unknown\\\",\\n }\\n }\\n\\n /// Parse what the host read, defaulting to [`Connection::Unknown`].\\n #[must_use]\\n pub fn from_str_or_unknown(value: &str) -> Self {\\n match value {\\n \\\"4g\\\" => Self::FourG,\\n \\\"3g\\\" => Self::ThreeG,\\n \\\"2g\\\" => Self::TwoG,\\n \\\"slow-2g\\\" => Self::SlowTwoG,\\n \\\"wifi\\\" => Self::Wifi,\\n _ => Self::Unknown,\\n }\\n }\\n}\\n\\n/// What kind of thing was measured.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum RumEventType {\\n /// The initial page load.\\n Pageload,\\n /// A user interaction.\\n Interaction,\\n /// A client-side navigation.\\n Navigation,\\n /// A network request.\\n Network,\\n /// An error.\\n Error,\\n}\\n\\nimpl RumEventType {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Pageload => \\\"pageload\\\",\\n Self::Interaction => \\\"interaction\\\",\\n Self::Navigation => \\\"navigation\\\",\\n Self::Network => \\\"network\\\",\\n Self::Error => \\\"error\\\",\\n }\\n }\\n}\\n\\n/// One measurement.\\n///\\n/// `url` is a PATH, never a full URL: a query string carries session tokens,\\n/// reset links and search terms, and this component's whole premise is that it\\n/// collects no PII.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct RumEvent {\\n /// What kind.\\n pub event_type: RumEventType,\\n /// When, in milliseconds since the Unix epoch.\\n pub timestamp_ms: f64,\\n /// The path, without query or fragment.\\n pub url: String,\\n /// Which mini-app.\\n pub mini_app: Option<String>,\\n /// Device class.\\n pub device: Device,\\n /// Connection class.\\n pub connection: Connection,\\n /// The numbers.\\n pub metrics: Vec<(String, f64)>,\\n}\\n\\n/// The viewport width below which a device is [`Device::Mobile`].\\npub const MOBILE_MAX_WIDTH: u32 = 640;\\n/// The viewport width below which a device is [`Device::Tablet`].\\npub const TABLET_MAX_WIDTH: u32 = 1024;\\n\\n/// Classify a viewport width.\\n#[must_use]\\npub fn device_for(viewport_width: u32) -> Device {\\n if viewport_width < MOBILE_MAX_WIDTH {\\n Device::Mobile\\n } else if viewport_width < TABLET_MAX_WIDTH {\\n Device::Tablet\\n } else {\\n Device::Desktop\\n }\\n}\\n\\n/// Whether this session is sampled.\\n///\\n/// `draw` is a value in `[0, 1)` from the host. The comparison matches the\\n/// `.ts`'s `Math.random() > sampleRate` inverted, so a rate of `0.0` samples\\n/// nothing and `1.0` samples everything — both exactly, which a `>=` on either\\n/// side would get wrong at one end.\\n#[must_use]\\npub fn should_sample(draw: f64, sample_rate: f64) -> bool {\\n draw < sample_rate\\n}\\n\\n/// Strip a URL to the path RUM is allowed to keep.\\n///\\n/// A query string is where session tokens, password-reset links and search terms\\n/// live. The `.ts` uses `window.location.pathname`, which already excludes them;\\n/// this exists so a host that has a full URL cannot accidentally hand one over.\\n#[must_use]\\npub fn path_only(url: &str) -> String {\\n let without_fragment = url.split('#').next().unwrap_or(\\\"\\\");\\n let without_query = without_fragment.split('?').next().unwrap_or(\\\"\\\");\\n // Keep the path from an absolute URL, dropping scheme and host.\\n match without_query.find(\\\"://\\\") {\\n Some(scheme_end) => {\\n let rest = &without_query[scheme_end + 3..];\\n match rest.find('/') {\\n Some(path_start) => rest[path_start..].to_owned(),\\n None => \\\"/\\\".to_owned(),\\n }\\n }\\n None => without_query.to_owned(),\\n }\\n}\\n\\n/// A bounded batch of events awaiting flush.\\n#[derive(Debug, Clone)]\\npub struct RumBuffer {\\n /// Whether this session is sampled at all.\\n sampled: bool,\\n /// How many events to hold before dropping the oldest.\\n max_events: usize,\\n events: Vec<RumEvent>,\\n}\\n\\nimpl RumBuffer {\\n /// A buffer for a session that has already been sampled in or out.\\n ///\\n /// An unsampled buffer DISCARDS. The `.ts` leaves `record()` callable on an\\n /// unsampled collector whose flush timer was never started, so events pile up\\n /// with nothing to drain them — in nine sessions out of ten at the default\\n /// rate.\\n #[must_use]\\n pub fn new(sampled: bool, max_events: usize) -> Self {\\n Self {\\n sampled,\\n max_events,\\n events: Vec::new(),\\n }\\n }\\n\\n /// Whether this session is collecting.\\n #[must_use]\\n pub const fn is_sampled(&self) -> bool {\\n self.sampled\\n }\\n\\n /// Record an event. Returns whether it was kept.\\n pub fn record(&mut self, event: RumEvent) -> bool {\\n if !self.sampled {\\n return false;\\n }\\n self.events.push(event);\\n // Dropping the oldest loses the least recent measurement rather than\\n // growing until the tab does. A flush that fails is swallowed by design\\n // — RUM must never surface its own network error to the user it measures\\n // — so without a ceiling an unreachable endpoint accumulates for the\\n // whole session.\\n if self.events.len() > self.max_events {\\n self.events.remove(0);\\n }\\n true\\n }\\n\\n /// Take everything pending, leaving the buffer empty.\\n ///\\n /// The batch is handed over BEFORE the host tries to send it, matching the\\n /// `.ts`. A send that fails loses that batch rather than retrying, which is\\n /// the deliberate trade for never blocking or surfacing an error.\\n pub fn drain(&mut self) -> Vec<RumEvent> {\\n std::mem::take(&mut self.events)\\n }\\n\\n /// How many events are pending.\\n #[must_use]\\n pub fn len(&self) -> usize {\\n self.events.len()\\n }\\n\\n /// Whether anything is pending.\\n #[must_use]\\n pub fn is_empty(&self) -> bool {\\n self.events.is_empty()\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1231
|
+
"mzizi-seo": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-seo\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"SEO metadata component for Next.js App Router. Generates head metadata, Open Graph tags, Twitter Cards, Schema.org JSON-LD structured data, and canonical URLs. Server component — runs at build/request time. Supports all Schema.org types used by the platform: JobPosting, VideoObject, MedicalWebPage, RentalCarReservation, Product, Event, Article, Recipe, etc.\",\"crate\":{\"name\":\"mzizi-discovery\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n11-discovery/mzizi-seo.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N11 discovery — \\\"if the machine can't see it, it doesn't exist.\\\"\\n//!\\n//! The Rust implementation of `mzizi-seo`: page metadata and Schema.org JSON-LD,\\n//! for a host that is not Next.js.\\n//!\\n//! # What this owns, and what the host owns\\n//!\\n//! This builds the metadata and renders it. It does not know what a framework is.\\n//! The `.ts` sibling returns a Next.js `Metadata` object and lets the App Router\\n//! render the tags; there is no App Router here, so [`Metadata::to_head_html`]\\n//! emits the `<meta>`/`<link>` elements and the host puts them in `<head>`. Same\\n//! division as N8, which builds an OTLP request and lets the host send it.\\n//!\\n//! # A security defect in the TypeScript, fixed here\\n//!\\n//! `generateSchemaLD` returns `JSON.stringify(...)` and its own doc comment tells\\n//! you to inject it with `dangerouslySetInnerHTML`. JSON escaping does not escape\\n//! `<`, so any schema field carrying the six characters `</script` closes the\\n//! script element early and everything after it is parsed as HTML. A job title, a\\n//! product name or an article headline is enough — all four templates take\\n//! caller-supplied strings, and a marketplace listing is user input.\\n//!\\n//! [`generate_schema_ld`] escapes `<` as `\\\\u003c` (and `>` as `\\\\u003e`, and `&`\\n//! as `\\\\u0026`). Those are valid JSON string escapes, so the JSON-LD parses\\n//! identically while becoming inert inside a script element. This is the standard\\n//! mitigation and it costs nothing.\\n//!\\n//! **The `.ts` sibling still has this defect.** It is not fixed by this file, and\\n//! it should be — tracked as part of the back-port of Rust-found defects.\\n//!\\n//! # Two things the TypeScript does that this deliberately does not\\n//!\\n//! **The `product` og:type dance.** The `.ts` sets `openGraph.type` to `website`\\n//! when the caller asked for `product`, then re-emits the true value through\\n//! Next's `other` map — because Next's `OpenGraph` union rejects `product`. That\\n//! is a workaround for one framework's types, not a fact about Open Graph, so\\n//! here [`OgType::Product`] simply emits `og:type` as `product` once.\\n//!\\n//! **`alternates.languages` built from a bare locale list.** Preserved as-is,\\n//! including that it emits no `x-default` and does not include the current locale\\n//! — changing either alters what crawlers do with a live site, which is a\\n//! product decision rather than a porting one.\\n\\nuse core::fmt::Write as _;\\n\\n/// What kind of thing the page is, for `og:type`.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\\npub enum OgType {\\n /// A generic page.\\n #[default]\\n Website,\\n /// A piece of writing.\\n Article,\\n /// A person or organisation.\\n Profile,\\n /// Something for sale. A real `og:type` (ogp.me/ns/product).\\n Product,\\n}\\n\\nimpl OgType {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Website => \\\"website\\\",\\n Self::Article => \\\"article\\\",\\n Self::Profile => \\\"profile\\\",\\n Self::Product => \\\"product\\\",\\n }\\n }\\n}\\n\\n/// How a link should render on X/Twitter.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\\npub enum TwitterCard {\\n /// Small thumbnail beside the text.\\n Summary,\\n /// Full-bleed image above the text.\\n #[default]\\n SummaryLargeImage,\\n /// An inline player.\\n Player,\\n}\\n\\nimpl TwitterCard {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Summary => \\\"summary\\\",\\n Self::SummaryLargeImage => \\\"summary_large_image\\\",\\n Self::Player => \\\"player\\\",\\n }\\n }\\n}\\n\\n/// The site this metadata describes. Matches the `.ts` module constants.\\npub const BASE_URL: &str = \\\"https://mukoko.com\\\";\\n/// Site name, appended to every title.\\npub const SITE_NAME: &str = \\\"Mukoko\\\";\\n/// Open Graph image used when a page supplies none.\\npub const DEFAULT_OG_IMAGE: &str = \\\"/og-default.png\\\";\\n\\n/// Everything a caller says about a page.\\n#[derive(Debug, Clone, Default)]\\npub struct SeoConfig {\\n /// Page title, before ` — Mukoko` is appended.\\n pub title: String,\\n /// Meta description. Omitted entirely when `None`.\\n pub description: Option<String>,\\n /// Site-root-relative path, e.g. `/jobs/123`. Joined to [`BASE_URL`].\\n pub canonical_url: Option<String>,\\n /// Open Graph image URL. Falls back to [`DEFAULT_OG_IMAGE`].\\n pub og_image: Option<String>,\\n /// What kind of thing this page is.\\n pub og_type: OgType,\\n /// How the link renders on X/Twitter.\\n pub twitter_card: TwitterCard,\\n /// Ask crawlers not to index or follow.\\n pub no_index: bool,\\n /// BCP 47 locale of this page.\\n pub locale: Option<String>,\\n /// Other locales this page exists in, for `alternates.languages`.\\n pub alternate_locales: Vec<String>,\\n}\\n\\n/// One `hreflang` alternate.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct Alternate {\\n /// The locale code.\\n pub locale: String,\\n /// The absolute URL for that locale.\\n pub url: String,\\n}\\n\\n/// Resolved page metadata: every value decided, nothing defaulted later.\\n///\\n/// The equivalent of the `.ts`'s Next `Metadata` return, minus the framework. A\\n/// host may read the fields directly or call [`Metadata::to_head_html`].\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct Metadata {\\n /// `\\\"<title> — Mukoko\\\"`.\\n pub title: String,\\n /// Meta description, if the caller gave one.\\n pub description: Option<String>,\\n /// Absolute canonical URL, if the caller gave a path.\\n pub canonical: Option<String>,\\n /// `hreflang` alternates, in the order the caller listed them.\\n pub alternates: Vec<Alternate>,\\n /// True when crawlers should be told to stay away.\\n pub no_index: bool,\\n /// `og:type`, emitted truthfully — see the module docs.\\n pub og_type: OgType,\\n /// `og:locale`.\\n pub locale: String,\\n /// The image URL used for both Open Graph and Twitter.\\n pub image: String,\\n /// Open Graph image width, fixed at 1200 as in the `.ts`.\\n pub image_width: u32,\\n /// Open Graph image height, fixed at 630 as in the `.ts`.\\n pub image_height: u32,\\n /// `og:image:alt` — the raw title, not the site-suffixed one.\\n pub image_alt: String,\\n /// Twitter card style.\\n pub twitter_card: TwitterCard,\\n}\\n\\n/// Resolve a [`SeoConfig`] into [`Metadata`], applying every default.\\n///\\n/// Mirrors the `.ts` `generateMetadata`, including that an empty `og_image`\\n/// string falls back to the default (the `.ts` uses `||`, not `??`).\\n#[must_use]\\npub fn generate_metadata(config: &SeoConfig) -> Metadata {\\n let full_title = format!(\\\"{} — {SITE_NAME}\\\", config.title);\\n\\n let image = match config.og_image.as_deref() {\\n Some(s) if !s.is_empty() => s.to_owned(),\\n _ => DEFAULT_OG_IMAGE.to_owned(),\\n };\\n\\n let canonical_path = config.canonical_url.as_deref().unwrap_or(\\\"\\\");\\n let canonical = config\\n .canonical_url\\n .as_deref()\\n .filter(|s| !s.is_empty())\\n .map(|path| format!(\\\"{BASE_URL}{path}\\\"));\\n\\n let alternates = config\\n .alternate_locales\\n .iter()\\n .map(|l| Alternate {\\n locale: l.clone(),\\n url: format!(\\\"{BASE_URL}/{l}{canonical_path}\\\"),\\n })\\n .collect();\\n\\n Metadata {\\n title: full_title,\\n description: config.description.clone(),\\n canonical,\\n alternates,\\n no_index: config.no_index,\\n og_type: config.og_type,\\n locale: config.locale.clone().unwrap_or_else(|| \\\"en\\\".to_owned()),\\n image,\\n image_width: 1200,\\n image_height: 630,\\n image_alt: config.title.clone(),\\n twitter_card: config.twitter_card,\\n }\\n}\\n\\n/// Escape a string for an HTML attribute value.\\n///\\n/// Attribute-position escaping, because every use below is inside `content=\\\"…\\\"`.\\n/// `&` first, or the ampersands introduced by the later replacements get escaped\\n/// a second time.\\nfn escape_attr(s: &str, out: &mut String) {\\n for c in s.chars() {\\n match c {\\n '&' => out.push_str(\\\"&\\\"),\\n '<' => out.push_str(\\\"<\\\"),\\n '>' => out.push_str(\\\">\\\"),\\n '\\\"' => out.push_str(\\\""\\\"),\\n '\\\\'' => out.push_str(\\\"'\\\"),\\n _ => out.push(c),\\n }\\n }\\n}\\n\\nfn meta_name(out: &mut String, name: &str, content: &str) {\\n out.push_str(\\\"<meta name=\\\\\\\"\\\");\\n out.push_str(name);\\n out.push_str(\\\"\\\\\\\" content=\\\\\\\"\\\");\\n escape_attr(content, out);\\n out.push_str(\\\"\\\\\\\">\\\\n\\\");\\n}\\n\\nfn meta_property(out: &mut String, property: &str, content: &str) {\\n out.push_str(\\\"<meta property=\\\\\\\"\\\");\\n out.push_str(property);\\n out.push_str(\\\"\\\\\\\" content=\\\\\\\"\\\");\\n escape_attr(content, out);\\n out.push_str(\\\"\\\\\\\">\\\\n\\\");\\n}\\n\\nimpl Metadata {\\n /// Render the `<title>`, `<meta>` and `<link>` elements for `<head>`.\\n ///\\n /// Every caller-supplied value is attribute-escaped. The `.ts` never had to\\n /// do this because React escaped for it; a Rust host writing a string into\\n /// `<head>` has no such help, so it happens here rather than being left as an\\n /// exercise for whoever renders.\\n #[must_use]\\n pub fn to_head_html(&self) -> String {\\n let mut out = String::new();\\n\\n out.push_str(\\\"<title>\\\");\\n escape_attr(&self.title, &mut out);\\n out.push_str(\\\"</title>\\\\n\\\");\\n\\n if let Some(d) = &self.description {\\n meta_name(&mut out, \\\"description\\\", d);\\n }\\n if self.no_index {\\n meta_name(&mut out, \\\"robots\\\", \\\"noindex, nofollow\\\");\\n }\\n if let Some(c) = &self.canonical {\\n out.push_str(\\\"<link rel=\\\\\\\"canonical\\\\\\\" href=\\\\\\\"\\\");\\n escape_attr(c, &mut out);\\n out.push_str(\\\"\\\\\\\">\\\\n\\\");\\n }\\n for alt in &self.alternates {\\n out.push_str(\\\"<link rel=\\\\\\\"alternate\\\\\\\" hreflang=\\\\\\\"\\\");\\n escape_attr(&alt.locale, &mut out);\\n out.push_str(\\\"\\\\\\\" href=\\\\\\\"\\\");\\n escape_attr(&alt.url, &mut out);\\n out.push_str(\\\"\\\\\\\">\\\\n\\\");\\n }\\n\\n meta_property(&mut out, \\\"og:title\\\", &self.title);\\n if let Some(d) = &self.description {\\n meta_property(&mut out, \\\"og:description\\\", d);\\n }\\n if let Some(c) = &self.canonical {\\n meta_property(&mut out, \\\"og:url\\\", c);\\n }\\n meta_property(&mut out, \\\"og:site_name\\\", SITE_NAME);\\n meta_property(&mut out, \\\"og:type\\\", self.og_type.as_str());\\n meta_property(&mut out, \\\"og:locale\\\", &self.locale);\\n meta_property(&mut out, \\\"og:image\\\", &self.image);\\n let mut buf = String::new();\\n let _ = write!(buf, \\\"{}\\\", self.image_width);\\n meta_property(&mut out, \\\"og:image:width\\\", &buf);\\n buf.clear();\\n let _ = write!(buf, \\\"{}\\\", self.image_height);\\n meta_property(&mut out, \\\"og:image:height\\\", &buf);\\n meta_property(&mut out, \\\"og:image:alt\\\", &self.image_alt);\\n\\n meta_name(&mut out, \\\"twitter:card\\\", self.twitter_card.as_str());\\n meta_name(&mut out, \\\"twitter:title\\\", &self.title);\\n if let Some(d) = &self.description {\\n meta_name(&mut out, \\\"twitter:description\\\", d);\\n }\\n meta_name(&mut out, \\\"twitter:image\\\", &self.image);\\n\\n out\\n }\\n}\\n\\n// ─── Schema.org ─────────────────────────────────────────────────────────────\\n\\n/// A JSON value, enough for Schema.org and no more.\\n///\\n/// Hand-rolled rather than `serde_json`, for the reason N8's crate states at\\n/// length: these components are compiled into every consumer, and JSON-LD is a\\n/// small, fully specified shape.\\n///\\n/// `Object` is a `Vec` of pairs, not a map, because Schema.org output is read by\\n/// people as often as machines and insertion order is worth keeping.\\n#[derive(Debug, Clone, PartialEq)]\\npub enum Json {\\n /// A string.\\n Str(String),\\n /// A number. JSON has one numeric type.\\n Num(f64),\\n /// A boolean.\\n Bool(bool),\\n /// An ordered list.\\n Arr(Vec<Json>),\\n /// An ordered set of key/value pairs.\\n Object(Vec<(String, Json)>),\\n}\\n\\nimpl Json {\\n /// Convenience for `Json::Str`.\\n #[must_use]\\n pub fn s(v: impl Into<String>) -> Self {\\n Self::Str(v.into())\\n }\\n\\n /// Build an object from pairs.\\n #[must_use]\\n pub fn obj<K: Into<String>>(pairs: impl IntoIterator<Item = (K, Self)>) -> Self {\\n Self::Object(pairs.into_iter().map(|(k, v)| (k.into(), v)).collect())\\n }\\n\\n fn write(&self, out: &mut String) {\\n match self {\\n Self::Str(s) => write_json_string(s, out),\\n Self::Num(n) => {\\n // JSON has no NaN or Infinity. Emitting one produces a document\\n // no parser accepts, so they become null — the same choice\\n // `JSON.stringify` makes, which keeps the two siblings agreeing.\\n if n.is_finite() {\\n let _ = write!(out, \\\"{n}\\\");\\n } else {\\n out.push_str(\\\"null\\\");\\n }\\n }\\n Self::Bool(b) => out.push_str(if *b { \\\"true\\\" } else { \\\"false\\\" }),\\n Self::Arr(items) => {\\n out.push('[');\\n for (i, item) in items.iter().enumerate() {\\n if i > 0 {\\n out.push(',');\\n }\\n item.write(out);\\n }\\n out.push(']');\\n }\\n Self::Object(pairs) => {\\n out.push('{');\\n for (i, (k, v)) in pairs.iter().enumerate() {\\n if i > 0 {\\n out.push(',');\\n }\\n write_json_string(k, out);\\n out.push(':');\\n v.write(out);\\n }\\n out.push('}');\\n }\\n }\\n }\\n}\\n\\n/// Write a JSON string literal, escaped so it is also inert inside `<script>`.\\n///\\n/// Beyond the escapes JSON requires, `<`, `>` and `&` become `\\\\u003c`, `\\\\u003e`\\n/// and `\\\\u0026`. All three are valid JSON escapes, so a parser sees the original\\n/// characters — but the literal text `</script>` can no longer appear in the\\n/// output, which is what stops a caller-supplied string from closing the script\\n/// element that carries it. See the module docs.\\nfn write_json_string(s: &str, out: &mut String) {\\n out.push('\\\"');\\n for c in s.chars() {\\n match c {\\n '\\\"' => out.push_str(\\\"\\\\\\\\\\\\\\\"\\\"),\\n '\\\\\\\\' => out.push_str(\\\"\\\\\\\\\\\\\\\\\\\"),\\n '\\\\n' => out.push_str(\\\"\\\\\\\\n\\\"),\\n '\\\\r' => out.push_str(\\\"\\\\\\\\r\\\"),\\n '\\\\t' => out.push_str(\\\"\\\\\\\\t\\\"),\\n '\\\\u{8}' => out.push_str(\\\"\\\\\\\\b\\\"),\\n '\\\\u{c}' => out.push_str(\\\"\\\\\\\\f\\\"),\\n '<' => out.push_str(\\\"\\\\\\\\u003c\\\"),\\n '>' => out.push_str(\\\"\\\\\\\\u003e\\\"),\\n '&' => out.push_str(\\\"\\\\\\\\u0026\\\"),\\n // U+2028 and U+2029 are legal in JSON strings and illegal in\\n // JavaScript source. A JSON-LD block is parsed as JSON, but the same\\n // string reaching a `<script>` of another type would break it.\\n '\\\\u{2028}' => out.push_str(\\\"\\\\\\\\u2028\\\"),\\n '\\\\u{2029}' => out.push_str(\\\"\\\\\\\\u2029\\\"),\\n c if (c as u32) < 0x20 => {\\n let _ = write!(out, \\\"\\\\\\\\u{:04x}\\\", c as u32);\\n }\\n c => out.push(c),\\n }\\n }\\n out.push('\\\"');\\n}\\n\\n/// One Schema.org node: an `@type` plus its properties.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct SchemaOrg {\\n /// The `@type` value, e.g. `JobPosting`.\\n pub ty: String,\\n /// Everything else, in order.\\n pub properties: Vec<(String, Json)>,\\n}\\n\\nimpl SchemaOrg {\\n /// A node of the given type with no properties yet.\\n #[must_use]\\n pub fn new(ty: impl Into<String>) -> Self {\\n Self {\\n ty: ty.into(),\\n properties: Vec::new(),\\n }\\n }\\n\\n /// Add a property, builder style.\\n #[must_use]\\n pub fn with(mut self, key: impl Into<String>, value: Json) -> Self {\\n self.properties.push((key.into(), value));\\n self\\n }\\n\\n /// Add a property only when the option is `Some`, mirroring the `.ts`\\n /// spread-if-present idiom.\\n #[must_use]\\n pub fn with_opt(self, key: impl Into<String>, value: Option<Json>) -> Self {\\n match value {\\n Some(v) => self.with(key, v),\\n None => self,\\n }\\n }\\n\\n fn to_json(&self, with_context: bool) -> Json {\\n let mut pairs: Vec<(String, Json)> = Vec::with_capacity(self.properties.len() + 2);\\n if with_context {\\n pairs.push((\\\"@context\\\".to_owned(), Json::s(\\\"https://schema.org\\\")));\\n }\\n pairs.push((\\\"@type\\\".to_owned(), Json::s(self.ty.clone())));\\n pairs.extend(self.properties.iter().cloned());\\n Json::Object(pairs)\\n }\\n}\\n\\n/// Serialise one or more Schema.org nodes as a JSON-LD document.\\n///\\n/// One node yields that node with `@context`. Several yield a single `@context`\\n/// and an `@graph`.\\n///\\n/// # Divergence from the TypeScript\\n///\\n/// The `.ts` puts `@context` on the wrapper AND on every member of `@graph`. A\\n/// `@graph` inherits the enclosing context, so the repeats are redundant — this\\n/// emits it once, which is the canonical form. No consumer sees a different\\n/// graph; it is the same document with less in it.\\n#[must_use]\\npub fn generate_schema_ld(schema: &[SchemaOrg]) -> String {\\n let mut out = String::new();\\n match schema {\\n [] => {\\n // The `.ts` cannot reach this: its parameter is one node or a\\n // non-empty array in practice. An empty slice is expressible here,\\n // and an empty JSON-LD block is the only honest answer — a `@graph`\\n // of nothing would claim a structure that is not there.\\n Json::Object(vec![(\\\"@context\\\".to_owned(), Json::s(\\\"https://schema.org\\\"))])\\n .write(&mut out);\\n }\\n [only] => only.to_json(true).write(&mut out),\\n many => {\\n let graph = many.iter().map(|n| n.to_json(false)).collect::<Vec<_>>();\\n Json::Object(vec![\\n (\\\"@context\\\".to_owned(), Json::s(\\\"https://schema.org\\\")),\\n (\\\"@graph\\\".to_owned(), Json::Arr(graph)),\\n ])\\n .write(&mut out);\\n }\\n }\\n out\\n}\\n\\n/// A pay range on a [`job_posting`].\\n#[derive(Debug, Clone, PartialEq)]\\npub struct Salary {\\n /// Lower bound.\\n pub min: f64,\\n /// Upper bound.\\n pub max: f64,\\n /// ISO 4217 code.\\n pub currency: String,\\n}\\n\\n/// A `JobPosting` node.\\n#[must_use]\\npub fn job_posting(\\n title: &str,\\n company: &str,\\n location: &str,\\n description: &str,\\n date_posted: &str,\\n salary: Option<&Salary>,\\n) -> SchemaOrg {\\n SchemaOrg::new(\\\"JobPosting\\\")\\n .with(\\\"title\\\", Json::s(title))\\n .with(\\n \\\"hiringOrganization\\\",\\n Json::obj([\\n (\\\"@type\\\", Json::s(\\\"Organization\\\")),\\n (\\\"name\\\", Json::s(company)),\\n ]),\\n )\\n .with(\\n \\\"jobLocation\\\",\\n Json::obj([(\\\"@type\\\", Json::s(\\\"Place\\\")), (\\\"address\\\", Json::s(location))]),\\n )\\n .with(\\\"description\\\", Json::s(description))\\n .with(\\\"datePosted\\\", Json::s(date_posted))\\n .with_opt(\\n \\\"baseSalary\\\",\\n salary.map(|s| {\\n Json::obj([\\n (\\\"@type\\\", Json::s(\\\"MonetaryAmount\\\")),\\n (\\\"currency\\\", Json::s(s.currency.clone())),\\n (\\n \\\"value\\\",\\n Json::obj([\\n (\\\"@type\\\", Json::s(\\\"QuantitativeValue\\\")),\\n (\\\"minValue\\\", Json::Num(s.min)),\\n (\\\"maxValue\\\", Json::Num(s.max)),\\n (\\\"unitText\\\", Json::s(\\\"MONTH\\\")),\\n ]),\\n ),\\n ])\\n }),\\n )\\n}\\n\\n/// An `Article` node.\\n#[must_use]\\npub fn article(title: &str, author: &str, date_published: &str, image: Option<&str>) -> SchemaOrg {\\n SchemaOrg::new(\\\"Article\\\")\\n .with(\\\"headline\\\", Json::s(title))\\n .with(\\n \\\"author\\\",\\n Json::obj([(\\\"@type\\\", Json::s(\\\"Person\\\")), (\\\"name\\\", Json::s(author))]),\\n )\\n .with(\\\"datePublished\\\", Json::s(date_published))\\n .with_opt(\\\"image\\\", image.map(Json::s))\\n}\\n\\n/// An `Event` node.\\n#[must_use]\\npub fn event(\\n name: &str,\\n start_date: &str,\\n end_date: Option<&str>,\\n location: &str,\\n description: Option<&str>,\\n) -> SchemaOrg {\\n SchemaOrg::new(\\\"Event\\\")\\n .with(\\\"name\\\", Json::s(name))\\n .with(\\\"startDate\\\", Json::s(start_date))\\n .with_opt(\\\"endDate\\\", end_date.map(Json::s))\\n .with(\\n \\\"location\\\",\\n Json::obj([(\\\"@type\\\", Json::s(\\\"Place\\\")), (\\\"name\\\", Json::s(location))]),\\n )\\n .with_opt(\\\"description\\\", description.map(Json::s))\\n}\\n\\n/// A `Product` node.\\n#[must_use]\\npub fn product(\\n name: &str,\\n price: f64,\\n currency: &str,\\n image: Option<&str>,\\n description: Option<&str>,\\n) -> SchemaOrg {\\n SchemaOrg::new(\\\"Product\\\")\\n .with(\\\"name\\\", Json::s(name))\\n .with_opt(\\\"description\\\", description.map(Json::s))\\n .with_opt(\\\"image\\\", image.map(Json::s))\\n .with(\\n \\\"offers\\\",\\n Json::obj([\\n (\\\"@type\\\", Json::s(\\\"Offer\\\")),\\n (\\\"price\\\", Json::Num(price)),\\n (\\\"priceCurrency\\\", Json::s(currency)),\\n (\\\"availability\\\", Json::s(\\\"https://schema.org/InStock\\\")),\\n ]),\\n )\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n fn cfg(title: &str) -> SeoConfig {\\n SeoConfig {\\n title: title.to_owned(),\\n ..SeoConfig::default()\\n }\\n }\\n\\n #[test]\\n fn title_is_suffixed_and_alt_text_is_not() {\\n let m = generate_metadata(&cfg(\\\"Jobs\\\"));\\n assert_eq!(m.title, \\\"Jobs — Mukoko\\\");\\n assert_eq!(m.image_alt, \\\"Jobs\\\");\\n }\\n\\n #[test]\\n fn defaults_match_the_typescript() {\\n let m = generate_metadata(&cfg(\\\"x\\\"));\\n assert_eq!(m.og_type, OgType::Website);\\n assert_eq!(m.twitter_card, TwitterCard::SummaryLargeImage);\\n assert_eq!(m.locale, \\\"en\\\");\\n assert_eq!(m.image, DEFAULT_OG_IMAGE);\\n assert!(!m.no_index);\\n assert!(m.canonical.is_none());\\n }\\n\\n #[test]\\n fn empty_og_image_falls_back_like_the_ts_or_operator() {\\n let m = generate_metadata(&SeoConfig {\\n og_image: Some(String::new()),\\n ..cfg(\\\"x\\\")\\n });\\n assert_eq!(m.image, DEFAULT_OG_IMAGE);\\n }\\n\\n #[test]\\n fn canonical_and_alternates_are_absolute() {\\n let m = generate_metadata(&SeoConfig {\\n canonical_url: Some(\\\"/jobs/1\\\".to_owned()),\\n alternate_locales: vec![\\\"sn\\\".to_owned(), \\\"nd\\\".to_owned()],\\n ..cfg(\\\"x\\\")\\n });\\n assert_eq!(m.canonical.as_deref(), Some(\\\"https://mukoko.com/jobs/1\\\"));\\n assert_eq!(m.alternates[0].url, \\\"https://mukoko.com/sn/jobs/1\\\");\\n assert_eq!(m.alternates[1].url, \\\"https://mukoko.com/nd/jobs/1\\\");\\n }\\n\\n #[test]\\n fn product_emits_its_true_og_type_once() {\\n let m = generate_metadata(&SeoConfig {\\n og_type: OgType::Product,\\n ..cfg(\\\"Sofa\\\")\\n });\\n let html = m.to_head_html();\\n assert!(html.contains(r#\\\"<meta property=\\\"og:type\\\" content=\\\"product\\\">\\\"#));\\n assert!(!html.contains(r#\\\"content=\\\"website\\\"\\\"#));\\n assert_eq!(html.matches(\\\"og:type\\\").count(), 1);\\n }\\n\\n #[test]\\n fn head_html_escapes_attribute_values() {\\n let m = generate_metadata(&cfg(r#\\\"Bolt \\\" & <b>\\\"#));\\n let html = m.to_head_html();\\n assert!(!html.contains(\\\"<b>\\\"));\\n assert!(html.contains(\\\"<b>\\\"));\\n assert!(html.contains(\\\""\\\"));\\n assert!(html.contains(\\\"&\\\"));\\n }\\n\\n #[test]\\n fn no_index_emits_robots() {\\n let m = generate_metadata(&SeoConfig {\\n no_index: true,\\n ..cfg(\\\"x\\\")\\n });\\n assert!(\\n m.to_head_html()\\n .contains(r#\\\"name=\\\"robots\\\" content=\\\"noindex, nofollow\\\"\\\"#)\\n );\\n }\\n\\n #[test]\\n fn single_node_carries_the_context() {\\n let json = generate_schema_ld(&[article(\\\"T\\\", \\\"A\\\", \\\"2026-01-01\\\", None)]);\\n assert!(json.starts_with(r#\\\"{\\\"@context\\\":\\\"https://schema.org\\\",\\\"@type\\\":\\\"Article\\\"\\\"#));\\n assert!(!json.contains(\\\"@graph\\\"));\\n }\\n\\n #[test]\\n fn graph_carries_the_context_exactly_once() {\\n let json = generate_schema_ld(&[\\n article(\\\"T\\\", \\\"A\\\", \\\"2026-01-01\\\", None),\\n event(\\\"E\\\", \\\"2026-02-01\\\", None, \\\"Harare\\\", None),\\n ]);\\n assert_eq!(\\n json.matches(\\\"@context\\\").count(),\\n 1,\\n \\\"the .ts repeats it per member\\\"\\n );\\n assert!(json.contains(\\\"@graph\\\"));\\n }\\n\\n #[test]\\n fn script_closing_sequence_cannot_survive_into_the_document() {\\n // The defect this file fixes: a caller-supplied string ending the script\\n // element that carries it.\\n let json = generate_schema_ld(&[article(\\n \\\"Great deal</script><script>alert(1)</script>\\\",\\n \\\"A\\\",\\n \\\"2026-01-01\\\",\\n None,\\n )]);\\n assert!(!json.contains(\\\"</script>\\\"));\\n assert!(!json.contains('<'));\\n assert!(json.contains(\\\"\\\\\\\\u003c/script\\\\\\\\u003e\\\"));\\n }\\n\\n #[test]\\n fn optional_fields_are_omitted_not_nulled() {\\n let json = generate_schema_ld(&[product(\\\"Sofa\\\", 100.0, \\\"USD\\\", None, None)]);\\n assert!(!json.contains(\\\"image\\\"));\\n assert!(!json.contains(\\\"description\\\"));\\n assert!(json.contains(r#\\\"\\\"price\\\":100\\\"#));\\n }\\n\\n #[test]\\n fn salary_nests_a_quantitative_value() {\\n let s = Salary {\\n min: 500.0,\\n max: 900.0,\\n currency: \\\"USD\\\".to_owned(),\\n };\\n let json = generate_schema_ld(&[job_posting(\\n \\\"Dev\\\",\\n \\\"Nyuchi\\\",\\n \\\"Harare\\\",\\n \\\"d\\\",\\n \\\"2026-01-01\\\",\\n Some(&s),\\n )]);\\n assert!(json.contains(r#\\\"\\\"@type\\\":\\\"MonetaryAmount\\\"\\\"#));\\n assert!(json.contains(r#\\\"\\\"minValue\\\":500\\\"#));\\n assert!(json.contains(r#\\\"\\\"unitText\\\":\\\"MONTH\\\"\\\"#));\\n }\\n\\n #[test]\\n fn non_finite_numbers_become_null_like_json_stringify() {\\n let json = generate_schema_ld(&[product(\\\"x\\\", f64::NAN, \\\"USD\\\", None, None)]);\\n assert!(json.contains(r#\\\"\\\"price\\\":null\\\"#));\\n }\\n\\n #[test]\\n fn empty_slice_yields_a_bare_context_not_an_empty_graph() {\\n let json = generate_schema_ld(&[]);\\n assert_eq!(json, r#\\\"{\\\"@context\\\":\\\"https://schema.org\\\"}\\\"#);\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1232
|
+
"mzizi-stats-row": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-stats-row\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Horizontal compact stats bar for dashboards, home screens, and admin views. Displays 2–4 metric blocks inline with icon, label, value, and optional trend indicator. Uses mineral-colored icon backgrounds. The brand-standard way to show key metrics at a glance across all Mukoko apps.\",\"crate\":{\"name\":\"mzizi-brand\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n3-brand/mzizi-stats-row.rs\",\"type\":\"registry:rust\",\"content\":\"//! MZIZI STATS ROW — N3 brand, Dioxus. A Mzizi Roots component.\\n//!\\n//! The Rust sibling of `mzizi-stats-row.tsx`: the compact metrics bar on home screens and\\n//! dashboards — two to four stats, each with an icon in a translucent mineral square, a value,\\n//! a label and an optional trend. Same `data-slot`, same `data-layout`, same `inline` and\\n//! `grid` layouts, same trend colouring.\\n//!\\n//! # `layoutVariants` is a `match`\\n//!\\n//! The `.tsx` builds the layout classes with `class-variance-authority`; [`StatsLayout`] is the\\n//! same table, checked exhaustively at compile time, as `badge.rs` and `button.rs` do in N2.\\n//!\\n//! # The icon is an element\\n//!\\n//! The `.tsx` takes a Lucide component type per stat and renders it at `size-4` in the\\n//! stat's colour. [`StatItem::icon`] is an [`Element`]: the host passes its own icon, and the\\n//! square still tints it through `color`.\\n//!\\n//! # The harness's motion preference is a prop\\n//!\\n//! [`MziziStatsRowProps::prefers_reduced_motion`] replaces `useMziziHarness`'s\\n//! `motion.prefersReduced`, as `mzizi-update-prompt.rs` does in N7.\\n//!\\n//! # The portal link is on both branches\\n//!\\n//! The `.tsx` sets `data-portal` on the loading skeleton only. This port sets it on the loaded\\n//! view too, so the link from rendered markup back to the registry page does not depend on\\n//! whether the data has arrived.\\n\\nuse dioxus::prelude::*;\\n\\n/// The component's contract, in the clause grammar of the Mzizi language's `contract … end`\\n/// block (mzizi-dev/mzizi `design/RFC-0006-contracts.md`). `mzizi-brand`'s contract suite\\n/// evaluates every clause against this component's server-rendered markup and fails on any\\n/// clause it cannot evaluate.\\npub const CONTRACT: &str = r#\\\"contract\\n slot is \\\"mzizi-stats-row\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-stats-row\\\"\\n role is \\\"group\\\"\\n label is \\\"Statistics\\\"\\n layout is \\\"inline\\\"\\n stats_layout.inline class uses \\\"--radius-card\\\"\\n every trend_direction class in \\\"text-emerald-400\\\" \\\"text-red-400\\\" \\\"text-muted-foreground\\\"\\nend\\\"#;\\n\\n/// Layout. Matches `layoutVariants` in the `.tsx`.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]\\npub enum StatsLayout {\\n /// One card, stats side by side.\\n #[default]\\n Inline,\\n /// One card per stat, in a grid.\\n Grid,\\n}\\n\\nimpl StatsLayout {\\n /// Every layout, for exhaustive checks.\\n pub const ALL: [Self; 2] = [Self::Inline, Self::Grid];\\n\\n /// The layout's container classes.\\n #[must_use]\\n pub const fn classes(self) -> &'static str {\\n match self {\\n Self::Inline => {\\n \\\"flex flex-wrap items-center gap-4 rounded-[var(--radius-card,14px)] bg-card p-3 ring-1 ring-foreground/10\\\"\\n }\\n Self::Grid => \\\"grid gap-3\\\",\\n }\\n }\\n\\n /// The `data-layout` value.\\n #[must_use]\\n pub const fn slug(self) -> &'static str {\\n match self {\\n Self::Inline => \\\"inline\\\",\\n Self::Grid => \\\"grid\\\",\\n }\\n }\\n}\\n\\n/// Grid columns, used only by [`StatsLayout::Grid`]. Matches `gridColsMap`.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]\\npub enum StatsColumns {\\n /// Two columns.\\n #[default]\\n Two,\\n /// Three columns.\\n Three,\\n /// Two on mobile, four from `sm`.\\n Four,\\n}\\n\\nimpl StatsColumns {\\n /// The grid column classes.\\n #[must_use]\\n pub const fn classes(self) -> &'static str {\\n match self {\\n Self::Two => \\\"grid-cols-2\\\",\\n Self::Three => \\\"grid-cols-3\\\",\\n Self::Four => \\\"grid-cols-2 sm:grid-cols-4\\\",\\n }\\n }\\n}\\n\\n/// Which way a trend string points: a leading `+` is up, a leading `-` is down.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\\npub enum TrendDirection {\\n /// Starts with `+`.\\n Up,\\n /// Starts with `-`.\\n Down,\\n /// Anything else.\\n Flat,\\n}\\n\\nimpl TrendDirection {\\n /// Every direction, for exhaustive checks.\\n pub const ALL: [Self; 3] = [Self::Up, Self::Down, Self::Flat];\\n\\n /// Classify a trend string the way the `.tsx`'s `startsWith` checks do.\\n #[must_use]\\n pub fn of(trend: &str) -> Self {\\n if trend.starts_with('+') {\\n Self::Up\\n } else if trend.starts_with('-') {\\n Self::Down\\n } else {\\n Self::Flat\\n }\\n }\\n\\n /// The trend's colour class in the grid layout.\\n #[must_use]\\n pub const fn class(self) -> &'static str {\\n match self {\\n Self::Up => \\\"text-emerald-400\\\",\\n Self::Down => \\\"text-red-400\\\",\\n Self::Flat => \\\"text-muted-foreground\\\",\\n }\\n }\\n\\n /// The trend's colour class in the inline layout, which leaves a flat trend uncoloured.\\n #[must_use]\\n pub const fn inline_class(self) -> &'static str {\\n match self {\\n Self::Up => \\\"text-emerald-400\\\",\\n Self::Down => \\\"text-red-400\\\",\\n Self::Flat => \\\"\\\",\\n }\\n }\\n}\\n\\n/// The default icon square colour.\\npub const DEFAULT_COLOR: &str = \\\"var(--color-malachite)\\\";\\n\\n/// One stat.\\n#[derive(Clone, PartialEq)]\\npub struct StatItem {\\n /// Icon, tinted with `color`.\\n pub icon: Element,\\n /// Short label, e.g. \\\"Active events\\\".\\n pub label: String,\\n /// Display value, e.g. \\\"12\\\" or \\\"$450\\\".\\n pub value: String,\\n /// Mineral for the icon square; defaults to malachite.\\n pub color: Option<String>,\\n /// Trend, e.g. \\\"+12%\\\".\\n pub trend: Option<String>,\\n}\\n\\n/// The entrance animation, empty when motion is reduced — the `.tsx`'s `animStyle`, with the\\n/// harness's 200ms decelerate curve.\\n#[must_use]\\npub fn entrance_style(prefers_reduced_motion: bool) -> &'static str {\\n if prefers_reduced_motion {\\n \\\"\\\"\\n } else {\\n \\\"animation: nyuchi-fade-slide-up 200ms cubic-bezier(0, 0, 0.2, 1) both;\\\"\\n }\\n}\\n\\n/// The container classes: the layout, the grid columns when gridded, then the consumer's.\\n#[must_use]\\npub fn container_classes(layout: StatsLayout, columns: StatsColumns, extra: &str) -> String {\\n let mut out = layout.classes().to_owned();\\n if layout == StatsLayout::Grid {\\n out.push(' ');\\n out.push_str(columns.classes());\\n }\\n if !extra.is_empty() {\\n out.push(' ');\\n out.push_str(extra);\\n }\\n out\\n}\\n\\n/// Props for [`MziziStatsRow`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziStatsRowProps {\\n /// Renders the skeleton instead of the stats.\\n #[props(default)]\\n pub loading: bool,\\n /// The stats, two to four.\\n pub stats: Vec<StatItem>,\\n /// Layout.\\n #[props(default)]\\n pub layout: StatsLayout,\\n /// Grid columns; ignored inline.\\n #[props(default)]\\n pub columns: StatsColumns,\\n /// Replaces the harness's `motion.prefersReduced`.\\n #[props(default)]\\n pub prefers_reduced_motion: bool,\\n /// Extra classes, appended last.\\n #[props(default)]\\n pub class: String,\\n}\\n\\n/// A compact row, or grid, of stats.\\n#[component]\\npub fn MziziStatsRow(props: MziziStatsRowProps) -> Element {\\n if props.loading {\\n return rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-stats-row\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-stats-row\\\",\\n \\\"data-loading\\\": \\\"\\\",\\n role: \\\"group\\\",\\n \\\"aria-label\\\": \\\"Statistics\\\",\\n class: \\\"flex animate-pulse gap-4\\\",\\n for i in 0..3 {\\n div { key: \\\"{i}\\\", class: \\\"flex-1 space-y-1.5 rounded-[var(--radius-md,12px)] bg-muted p-3\\\",\\n div { class: \\\"h-2.5 w-1/2 rounded bg-foreground/5\\\" }\\n div { class: \\\"h-5 w-2/3 rounded bg-foreground/5\\\" }\\n }\\n }\\n }\\n };\\n }\\n let grid = props.layout == StatsLayout::Grid;\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-stats-row\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-stats-row\\\",\\n style: entrance_style(props.prefers_reduced_motion),\\n role: \\\"group\\\",\\n \\\"aria-label\\\": \\\"Statistics\\\",\\n \\\"data-layout\\\": props.layout.slug(),\\n class: container_classes(props.layout, props.columns, &props.class),\\n for (i, stat) in props.stats.iter().enumerate() {\\n {\\n let color = stat.color.clone().unwrap_or_else(|| DEFAULT_COLOR.to_owned());\\n let square = format!(\\\"background-color: color-mix(in srgb, {color} 20%, transparent);\\\");\\n let tint = format!(\\\"color: {color};\\\");\\n let direction = stat.trend.as_deref().map(TrendDirection::of);\\n if grid {\\n rsx! {\\n div { key: \\\"{i}\\\", class: \\\"flex flex-col gap-2 rounded-[var(--radius-card,14px)] bg-card p-4 ring-1 ring-foreground/10\\\",\\n div { class: \\\"flex items-start justify-between\\\",\\n div { class: \\\"flex size-8 items-center justify-center rounded-[var(--radius-inner,7px)]\\\", style: \\\"{square}\\\",\\n span { class: \\\"size-4\\\", style: \\\"{tint}\\\", {stat.icon.clone()} }\\n }\\n if let (Some(trend), Some(d)) = (&stat.trend, direction) {\\n span { class: \\\"text-[11px] font-medium {d.class()}\\\", \\\"{trend}\\\" }\\n }\\n }\\n div { class: \\\"text-2xl font-bold text-foreground\\\", \\\"{stat.value}\\\" }\\n div { class: \\\"text-xs text-muted-foreground\\\", \\\"{stat.label}\\\" }\\n }\\n }\\n } else {\\n rsx! {\\n div { key: \\\"{i}\\\", class: \\\"flex min-w-[120px] items-center gap-2\\\",\\n div { class: \\\"flex size-8 shrink-0 items-center justify-center rounded-[var(--radius-inner,7px)]\\\", style: \\\"{square}\\\",\\n span { class: \\\"size-4\\\", style: \\\"{tint}\\\", {stat.icon.clone()} }\\n }\\n div { class: \\\"min-w-0\\\",\\n div { class: \\\"text-[10px] text-muted-foreground\\\", \\\"{stat.label}\\\" }\\n div { class: \\\"flex items-baseline gap-1.5\\\",\\n span { class: \\\"text-[13px] font-semibold text-foreground\\\", \\\"{stat.value}\\\" }\\n if let (Some(trend), Some(d)) = (&stat.trend, direction) {\\n span { class: \\\"text-[10px] font-medium {d.inline_class()}\\\", \\\"{trend}\\\" }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn trends_are_read_from_their_sign() {\\n assert_eq!(TrendDirection::of(\\\"+12%\\\"), TrendDirection::Up);\\n assert_eq!(TrendDirection::of(\\\"-3%\\\"), TrendDirection::Down);\\n assert_eq!(TrendDirection::of(\\\"0%\\\"), TrendDirection::Flat);\\n }\\n\\n #[test]\\n fn grid_columns_apply_only_to_the_grid() {\\n assert_eq!(\\n container_classes(StatsLayout::Inline, StatsColumns::Four, \\\"\\\"),\\n StatsLayout::Inline.classes()\\n );\\n assert_eq!(\\n container_classes(StatsLayout::Grid, StatsColumns::Four, \\\"mt-2\\\"),\\n \\\"grid gap-3 grid-cols-2 sm:grid-cols-4 mt-2\\\"\\n );\\n }\\n\\n #[test]\\n fn a_flat_trend_is_uncoloured_inline_and_muted_in_the_grid() {\\n assert_eq!(TrendDirection::Flat.inline_class(), \\\"\\\");\\n assert_eq!(TrendDirection::Flat.class(), \\\"text-muted-foreground\\\");\\n }\\n}\\n\"}],\"contract\":\"contract\\n slot is \\\"mzizi-stats-row\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-stats-row\\\"\\n role is \\\"group\\\"\\n label is \\\"Statistics\\\"\\n layout is \\\"inline\\\"\\n stats_layout.inline class uses \\\"--radius-card\\\"\\n every trend_direction class in \\\"text-emerald-400\\\" \\\"text-red-400\\\" \\\"text-muted-foreground\\\"\\nend\"}",
|
|
1233
|
+
"mzizi-success-screen": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-success-screen\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Branded success/confirmation screen with mineral-colored checkmark, title, message, and optional actions. Extracted from payment-page, checkout-page, and booking-page where the same checkmark + title pattern was repeated. Uses the Seven African Minerals for the success indicator.\",\"crate\":{\"name\":\"mzizi-brand\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n3-brand/mzizi-success-screen.rs\",\"type\":\"registry:rust\",\"content\":\"//! MZIZI SUCCESS SCREEN — N3 brand, Dioxus. A Mzizi Roots component.\\n//!\\n//! The Rust sibling of `mzizi-success-screen.tsx`: the confirmation shown after a payment,\\n//! a booking or a submission. Same `data-slot`, same polite live `role=\\\"status\\\"`, same default\\n//! check mark in the success mineral, same pill actions at the 48px floor.\\n//!\\n//! # The harness's motion preference is a prop\\n//!\\n//! [`MziziSuccessScreenProps::prefers_reduced_motion`] replaces `useMziziHarness`'s\\n//! `motion.prefersReduced`, as `mzizi-update-prompt.rs` does in N7.\\n\\nuse dioxus::prelude::*;\\n\\n/// The component's contract, in the clause grammar of the Mzizi language's `contract … end`\\n/// block (mzizi-dev/mzizi `design/RFC-0006-contracts.md`). `mzizi-brand`'s contract suite\\n/// evaluates every clause against this component's server-rendered markup and fails on any\\n/// clause it cannot evaluate.\\npub const CONTRACT: &str = r#\\\"contract\\n slot is \\\"mzizi-success-screen\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-success-screen\\\"\\n role is \\\"status\\\"\\n title is \\\"Success\\\"\\n button \\\"Done\\\" min_height 48\\n button \\\"View receipt\\\" min_height 48\\nend\\\"#;\\n\\n/// A labelled action button.\\n#[derive(Clone, PartialEq)]\\npub struct SuccessAction {\\n /// Button label.\\n pub label: String,\\n /// Fired on press.\\n pub on_press: EventHandler<()>,\\n}\\n\\n/// The entrance animation, empty when motion is reduced — the `.tsx`'s `animStyle`, with the\\n/// harness's 200ms decelerate curve.\\n#[must_use]\\npub fn entrance_style(prefers_reduced_motion: bool) -> &'static str {\\n if prefers_reduced_motion {\\n \\\"\\\"\\n } else {\\n \\\"animation: nyuchi-fade-slide-up 200ms cubic-bezier(0, 0, 0.2, 1) both;\\\"\\n }\\n}\\n\\n/// The success mineral, used for the default check mark.\\npub const SUCCESS_STROKE: &str = \\\"var(--status-success, var(--color-malachite, #64FFDA))\\\";\\n\\nconst PRIMARY: &str = \\\"min-h-[48px] rounded-full bg-[var(--color-cobalt,#00B0FF)] px-6 text-sm font-medium text-white transition-colors hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary,#00B0FF)]\\\";\\nconst SECONDARY: &str = \\\"min-h-[48px] rounded-full bg-muted px-6 text-sm font-medium text-foreground transition-colors hover:bg-muted/80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary,#00B0FF)]\\\";\\n\\n/// Props for [`MziziSuccessScreen`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziSuccessScreenProps {\\n /// Heading.\\n #[props(default = \\\"Success\\\".to_string(), into)]\\n pub title: String,\\n /// Supporting message.\\n #[props(default)]\\n pub message: Option<String>,\\n /// Extra detail below the message, e.g. a receipt summary.\\n #[props(default)]\\n pub detail: Option<Element>,\\n /// Primary action.\\n #[props(default)]\\n pub primary_action: Option<SuccessAction>,\\n /// Secondary action.\\n #[props(default)]\\n pub secondary_action: Option<SuccessAction>,\\n /// Replaces the default check mark.\\n #[props(default)]\\n pub icon: Option<Element>,\\n /// Replaces the harness's `motion.prefersReduced`.\\n #[props(default)]\\n pub prefers_reduced_motion: bool,\\n /// Extra classes, appended last.\\n #[props(default)]\\n pub class: String,\\n}\\n\\n/// A confirmation screen with a check mark and up to two actions.\\n#[component]\\npub fn MziziSuccessScreen(props: MziziSuccessScreenProps) -> Element {\\n let root = if props.class.is_empty() {\\n \\\"flex flex-col items-center gap-4 px-6 py-12 text-center\\\".to_owned()\\n } else {\\n format!(\\n \\\"flex flex-col items-center gap-4 px-6 py-12 text-center {}\\\",\\n props.class\\n )\\n };\\n let has_actions = props.primary_action.is_some() || props.secondary_action.is_some();\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-success-screen\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-success-screen\\\",\\n role: \\\"status\\\",\\n \\\"aria-live\\\": \\\"polite\\\",\\n style: entrance_style(props.prefers_reduced_motion),\\n class: \\\"{root}\\\",\\n div { class: \\\"flex size-16 items-center justify-center rounded-full bg-[var(--status-success,var(--color-malachite,#64FFDA))]/15\\\",\\n if let Some(icon) = &props.icon {\\n {icon}\\n } else {\\n svg {\\n width: \\\"32\\\",\\n height: \\\"32\\\",\\n view_box: \\\"0 0 24 24\\\",\\n fill: \\\"none\\\",\\n stroke: SUCCESS_STROKE,\\n stroke_width: \\\"2\\\",\\n stroke_linecap: \\\"round\\\",\\n stroke_linejoin: \\\"round\\\",\\n \\\"aria-hidden\\\": \\\"true\\\",\\n path { d: \\\"M5 12l5 5L20 7\\\" }\\n }\\n }\\n }\\n h2 { class: \\\"text-xl font-bold text-foreground\\\", style: \\\"font-family: var(--font-serif);\\\",\\n \\\"{props.title}\\\"\\n }\\n if let Some(message) = &props.message {\\n p { class: \\\"max-w-sm text-sm text-muted-foreground\\\", \\\"{message}\\\" }\\n }\\n if let Some(detail) = &props.detail {\\n {detail}\\n }\\n if has_actions {\\n div { class: \\\"mt-4 flex gap-3\\\",\\n if let Some(action) = &props.primary_action {\\n button {\\n r#type: \\\"button\\\",\\n class: PRIMARY,\\n onclick: {\\n let on_press = action.on_press;\\n move |_| on_press.call(())\\n },\\n \\\"{action.label}\\\"\\n }\\n }\\n if let Some(action) = &props.secondary_action {\\n button {\\n r#type: \\\"button\\\",\\n class: SECONDARY,\\n onclick: {\\n let on_press = action.on_press;\\n move |_| on_press.call(())\\n },\\n \\\"{action.label}\\\"\\n }\\n }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn both_actions_are_pills_at_the_touch_floor() {\\n for classes in [PRIMARY, SECONDARY] {\\n assert!(classes.split_whitespace().any(|c| c == \\\"min-h-[48px]\\\"));\\n assert!(classes.split_whitespace().any(|c| c == \\\"rounded-full\\\"));\\n }\\n }\\n\\n #[test]\\n fn the_check_mark_uses_the_success_mineral() {\\n assert!(SUCCESS_STROKE.starts_with(\\\"var(--status-success\\\"));\\n }\\n}\\n\"}],\"contract\":\"contract\\n slot is \\\"mzizi-success-screen\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-success-screen\\\"\\n role is \\\"status\\\"\\n title is \\\"Success\\\"\\n button \\\"Done\\\" min_height 48\\n button \\\"View receipt\\\" min_height 48\\nend\"}",
|
|
1234
|
+
"mzizi-suitability-card": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-suitability-card\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Branded suitability/recommendation card with score, icon, title, description, and mineral-colored score indicator. Universal pattern from mukoko-weather ActivityInsights. Used for: \\\"Good for farming\\\" (Weather), \\\"Good for families\\\" (Places), \\\"Good for outdoor exercise\\\" (Health), \\\"Safe to travel\\\" (Transport), \\\"Good match for you\\\" (Jobs). Any scored recommendation.\",\"crate\":{\"name\":\"mzizi-brand\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n3-brand/mzizi-suitability-card.rs\",\"type\":\"registry:rust\",\"content\":\"//! MZIZI SUITABILITY CARD — N3 brand, Dioxus. A Mzizi Roots component.\\n//!\\n//! The Rust sibling of `mzizi-suitability-card.tsx`: a scored recommendation — \\\"good for\\n//! drying laundry\\\", \\\"fair for a game drive\\\" — with the level mapped to a mineral. Same\\n//! `data-slot`, same `role=\\\"article\\\"`, same five levels, same score-or-label pill.\\n//!\\n//! # The harness's motion preference is a prop\\n//!\\n//! [`MziziSuitabilityCardProps::prefers_reduced_motion`] replaces `useMziziHarness`'s\\n//! `motion.prefersReduced`, as `mzizi-update-prompt.rs` does in N7.\\n\\nuse dioxus::prelude::*;\\n\\n/// The component's contract, in the clause grammar of the Mzizi language's `contract … end`\\n/// block (mzizi-dev/mzizi `design/RFC-0006-contracts.md`). `mzizi-brand`'s contract suite\\n/// evaluates every clause against this component's server-rendered markup and fails on any\\n/// clause it cannot evaluate.\\npub const CONTRACT: &str = r#\\\"contract\\n slot is \\\"mzizi-suitability-card\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-suitability-card\\\"\\n role is \\\"article\\\"\\n every suitability_level label not_empty\\n excellent.color uses \\\"--color-malachite\\\"\\n unsuitable.color uses \\\"--color-destructive\\\"\\nend\\\"#;\\n\\n/// How suitable the thing is. Matches `levelConfig` in the `.tsx`.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\\npub enum SuitabilityLevel {\\n /// Malachite.\\n Excellent,\\n /// Cobalt.\\n Good,\\n /// Gold.\\n Fair,\\n /// Terracotta.\\n Poor,\\n /// Destructive.\\n Unsuitable,\\n}\\n\\nimpl SuitabilityLevel {\\n /// Every level, for exhaustive checks.\\n pub const ALL: [Self; 5] = [\\n Self::Excellent,\\n Self::Good,\\n Self::Fair,\\n Self::Poor,\\n Self::Unsuitable,\\n ];\\n\\n /// The level's mineral.\\n #[must_use]\\n pub const fn color(self) -> &'static str {\\n match self {\\n Self::Excellent => \\\"var(--color-malachite, #64FFDA)\\\",\\n Self::Good => \\\"var(--color-cobalt, #00B0FF)\\\",\\n Self::Fair => \\\"var(--color-gold, #FFD740)\\\",\\n Self::Poor => \\\"var(--color-terracotta, #D4A574)\\\",\\n Self::Unsuitable => \\\"var(--color-destructive, #FF5252)\\\",\\n }\\n }\\n\\n /// The pill label when there is no score.\\n #[must_use]\\n pub const fn label(self) -> &'static str {\\n match self {\\n Self::Excellent => \\\"Excellent\\\",\\n Self::Good => \\\"Good\\\",\\n Self::Fair => \\\"Fair\\\",\\n Self::Poor => \\\"Poor\\\",\\n Self::Unsuitable => \\\"Unsuitable\\\",\\n }\\n }\\n\\n /// The value the `.tsx` accepts for `level`.\\n #[must_use]\\n pub const fn slug(self) -> &'static str {\\n match self {\\n Self::Excellent => \\\"excellent\\\",\\n Self::Good => \\\"good\\\",\\n Self::Fair => \\\"fair\\\",\\n Self::Poor => \\\"poor\\\",\\n Self::Unsuitable => \\\"unsuitable\\\",\\n }\\n }\\n}\\n\\n/// The pill text: the score as a percentage when there is one, else the level's label.\\n#[must_use]\\npub fn pill_text(level: SuitabilityLevel, score: Option<u8>) -> String {\\n score.map_or_else(|| level.label().to_owned(), |s| format!(\\\"{s}%\\\"))\\n}\\n\\n/// The tinted surface and text colour for the icon well and the pill.\\n#[must_use]\\npub fn tint_style(level: SuitabilityLevel) -> String {\\n let c = level.color();\\n format!(\\\"background-color: color-mix(in srgb, {c} 15%, transparent); color: {c};\\\")\\n}\\n\\n/// The entrance animation, empty when motion is reduced — the `.tsx`'s `animStyle`, with the\\n/// harness's 200ms decelerate curve.\\n#[must_use]\\npub fn entrance_style(prefers_reduced_motion: bool) -> &'static str {\\n if prefers_reduced_motion {\\n \\\"\\\"\\n } else {\\n \\\"animation: nyuchi-fade-slide-up 200ms cubic-bezier(0, 0, 0.2, 1) both;\\\"\\n }\\n}\\n\\nconst ROOT: &str = \\\"flex items-center gap-3 rounded-[var(--radius-lg,14px)] bg-card p-4 ring-1 ring-foreground/10 transition-shadow hover:shadow-md focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary,#00B0FF)]\\\";\\n\\n/// Props for [`MziziSuitabilityCard`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziSuitabilityCardProps {\\n /// Renders the skeleton instead of the card.\\n #[props(default)]\\n pub loading: bool,\\n /// Icon in a tinted well.\\n #[props(default)]\\n pub icon: Option<Element>,\\n /// What is being rated.\\n #[props(into)]\\n pub title: String,\\n /// Why.\\n #[props(default)]\\n pub description: Option<String>,\\n /// The rating.\\n pub level: SuitabilityLevel,\\n /// A score, shown as a percentage in place of the level label.\\n #[props(default)]\\n pub score: Option<u8>,\\n /// Replaces the harness's `motion.prefersReduced`.\\n #[props(default)]\\n pub prefers_reduced_motion: bool,\\n /// Extra classes, appended last.\\n #[props(default)]\\n pub class: String,\\n}\\n\\n/// A scored recommendation with a mineral-coded level.\\n#[component]\\npub fn MziziSuitabilityCard(props: MziziSuitabilityCardProps) -> Element {\\n if props.loading {\\n return rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-suitability-card\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-suitability-card\\\",\\n \\\"data-loading\\\": \\\"\\\",\\n role: \\\"article\\\",\\n class: \\\"flex animate-pulse items-center gap-3 rounded-[var(--radius-lg,14px)] bg-card p-4 ring-1 ring-foreground/10\\\",\\n div { class: \\\"size-10 rounded-[var(--radius-sm,7px)] bg-muted\\\" }\\n div { class: \\\"flex-1 space-y-1.5\\\",\\n div { class: \\\"h-3.5 w-1/2 rounded bg-muted\\\" }\\n div { class: \\\"h-2.5 w-2/3 rounded bg-muted\\\" }\\n }\\n div { class: \\\"h-6 w-16 rounded-full bg-muted\\\" }\\n }\\n };\\n }\\n let root = if props.class.is_empty() {\\n ROOT.to_owned()\\n } else {\\n format!(\\\"{ROOT} {}\\\", props.class)\\n };\\n let tint = tint_style(props.level);\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-suitability-card\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-suitability-card\\\",\\n \\\"data-level\\\": props.level.slug(),\\n role: \\\"article\\\",\\n style: entrance_style(props.prefers_reduced_motion),\\n class: \\\"{root}\\\",\\n if let Some(icon) = &props.icon {\\n div { class: \\\"flex size-10 shrink-0 items-center justify-center rounded-[var(--radius-sm,7px)] text-lg\\\", style: \\\"{tint}\\\",\\n {icon}\\n }\\n }\\n div { class: \\\"min-w-0 flex-1\\\",\\n p { class: \\\"text-sm font-medium text-foreground\\\", \\\"{props.title}\\\" }\\n if let Some(description) = &props.description {\\n p { class: \\\"mt-0.5 text-xs text-muted-foreground\\\", \\\"{description}\\\" }\\n }\\n }\\n span { class: \\\"shrink-0 rounded-full px-2.5 py-0.5 text-xs font-semibold\\\", style: \\\"{tint}\\\",\\n {pill_text(props.level, props.score)}\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn a_score_replaces_the_label() {\\n assert_eq!(pill_text(SuitabilityLevel::Good, Some(82)), \\\"82%\\\");\\n assert_eq!(pill_text(SuitabilityLevel::Good, None), \\\"Good\\\");\\n }\\n\\n #[test]\\n fn the_tint_mixes_the_level_colour() {\\n let s = tint_style(SuitabilityLevel::Fair);\\n assert!(s.contains(\\\"color-mix(in srgb, var(--color-gold, #FFD740) 15%, transparent)\\\"));\\n assert!(s.contains(\\\"color: var(--color-gold, #FFD740);\\\"));\\n }\\n}\\n\"}],\"contract\":\"contract\\n slot is \\\"mzizi-suitability-card\\\"\\n portal is \\\"https://mzizi.dev/components/mzizi-suitability-card\\\"\\n role is \\\"article\\\"\\n every suitability_level label not_empty\\n excellent.color uses \\\"--color-malachite\\\"\\n unsuitable.color uses \\\"--color-destructive\\\"\\nend\"}",
|
|
1235
|
+
"mzizi-synthetic-probe": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-synthetic-probe\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"Synthetic user journey probes that run on schedule. Defines step-by-step user flows (login → navigate → action → verify) and executes them against live or staging environments. Reports pass/fail with latency per step. Competitors: Datadog Synthetic Monitoring, Checkly.\",\"crate\":{\"name\":\"mzizi-assurance\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n8-assurance/mzizi-synthetic-probe.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N8 assurance — synthetic journeys: the probe contract and its pure parts.\\n//!\\n//! The Rust implementation of `mzizi-synthetic-probe`.\\n//!\\n//! # What this owns, and what it deliberately does not\\n//!\\n//! It owns the **contract** — what a journey is, what a run produced, when a\\n//! failure is worth waking somebody for — and every decision derivable from a\\n//! finished run. It does **not** own execution.\\n//!\\n//! That is the same boundary [`super::mzizi_otel`] draws at sending, and for the\\n//! same reason: driving a browser is I/O, and every host does it differently. A\\n//! Cloudflare Worker cannot spawn a browser process at all and reaches Browser\\n//! Run over `fetch`; a laptop can drive Chromium directly; a native shell has its\\n//! own harness. What must *not* vary is what counts as a pass, how step outcomes\\n//! roll up into a run outcome, and when an alert fires.\\n//!\\n//! The `.ts` sibling is honest about this in a way worth preserving: its\\n//! `executeSyntheticJourney` is a stub whose body says *\\\"in a real implementation\\n//! this would use Puppeteer/Playwright — here we define the contract that the\\n//! probe runner implements.\\\"* Porting that stub to Rust would have carried a\\n//! placeholder across as though it were an implementation. The contract is the\\n//! real content, so the contract is what moved.\\n//!\\n//! One correction landed on the way: that comment names Puppeteer/Playwright, and\\n//! **fundi is a Cloudflare Worker**, which can run neither. The runner that\\n//! actually exists (`scripts/kitesurf.ts`) is a `fetch` against Browser Run for\\n//! exactly that reason.\\n\\nuse std::collections::BTreeMap;\\n\\n/// What a step does when a runner executes it.\\n///\\n/// A closed set rather than a string, because a runner has to exhaustively\\n/// handle every variant — and an unknown step type should fail to compile rather\\n/// than be silently skipped at 3am.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum ProbeStepType {\\n /// Go to a path.\\n Navigate,\\n /// Click a selector.\\n Click,\\n /// Type a value into a selector.\\n Input,\\n /// Assert a selector is present.\\n Assert,\\n /// Wait, either for a selector or for a duration.\\n Wait,\\n /// Capture a screenshot.\\n Screenshot,\\n}\\n\\nimpl ProbeStepType {\\n /// The wire spelling, matching the `.ts` string union exactly.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Navigate => \\\"navigate\\\",\\n Self::Click => \\\"click\\\",\\n Self::Input => \\\"input\\\",\\n Self::Assert => \\\"assert\\\",\\n Self::Wait => \\\"wait\\\",\\n Self::Screenshot => \\\"screenshot\\\",\\n }\\n }\\n}\\n\\n/// One step a runner should perform.\\n///\\n/// This is the INPUT shape. The outcome of running it is [`StepResult`], which is\\n/// a different type carrying different fields — the `.ts` names this one\\n/// `ProbeStep` and leaves the outcome shape anonymous inside `ProbeResult`, which\\n/// is how the two came to be conflated in the first Rust draft.\\n#[derive(Debug, Clone)]\\npub struct ProbeStep {\\n /// What to do.\\n pub step_type: ProbeStepType,\\n /// Selector or path, depending on the step type.\\n pub target: Option<String>,\\n /// Value to enter, for [`ProbeStepType::Input`].\\n pub value: Option<String>,\\n /// Per-step timeout in milliseconds.\\n pub timeout_ms: Option<u32>,\\n /// Human description. Becomes the step's span name, so it is not decoration.\\n pub description: String,\\n}\\n\\n/// A journey a runner can execute on a schedule.\\n#[derive(Debug, Clone)]\\npub struct SyntheticJourney {\\n /// Stable id. A collector groups runs on this, so it must not drift.\\n pub id: String,\\n /// Human name.\\n pub name: String,\\n /// Which mini-app this journey exercises.\\n pub mini_app: Option<String>,\\n /// Which helix nodes the journey traverses.\\n ///\\n /// Uncapped on purpose: node numbers are labels, not a sequence, and any\\n /// upper bound here would be the defect rather than its current value.\\n pub nodes: Vec<u32>,\\n /// The steps, in order.\\n pub steps: Vec<ProbeStep>,\\n /// Cron expression, when scheduled.\\n pub schedule: Option<String>,\\n /// Regions to run from.\\n pub regions: Vec<String>,\\n /// Whether a failure should raise an alert.\\n pub alert_on_failure: bool,\\n}\\n\\n/// Outcome of one executed step.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum StepStatus {\\n /// The step asserted what it set out to assert.\\n Pass,\\n /// The step failed.\\n Fail,\\n}\\n\\nimpl StepStatus {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Pass => \\\"pass\\\",\\n Self::Fail => \\\"fail\\\",\\n }\\n }\\n}\\n\\n/// Outcome of a whole run.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub enum ProbeStatus {\\n /// Every step passed.\\n Pass,\\n /// At least one step failed.\\n Fail,\\n /// The run exceeded its allotted time.\\n Timeout,\\n /// The runner itself could not complete — distinct from the journey failing.\\n Error,\\n}\\n\\nimpl ProbeStatus {\\n /// Whether this outcome should surface as an OTLP `ERROR` status.\\n #[must_use]\\n pub const fn is_failure(self) -> bool {\\n !matches!(self, Self::Pass)\\n }\\n\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Pass => \\\"pass\\\",\\n Self::Fail => \\\"fail\\\",\\n Self::Timeout => \\\"timeout\\\",\\n Self::Error => \\\"error\\\",\\n }\\n }\\n}\\n\\n/// The outcome of one executed step.\\n#[derive(Debug, Clone)]\\npub struct StepResult {\\n /// The step's description, carried through so a reader need not re-join.\\n pub description: String,\\n /// Whether it passed.\\n pub status: StepStatus,\\n /// How long it took.\\n pub duration_ms: f64,\\n /// Why it failed, when it did.\\n ///\\n /// Carried onto the wire as `error.message` rather than left in prose,\\n /// because the consumer acting on it is usually not a human. A render check\\n /// that failed because the URL sat behind an auth wall is not the same\\n /// incident as a page that rendered empty, and fundi filing an issue for the\\n /// first would be a false positive.\\n pub error: Option<String>,\\n}\\n\\n/// What one run produced. The contract every runner returns.\\n#[derive(Debug, Clone)]\\npub struct ProbeResult {\\n /// Which journey ran.\\n pub journey_id: String,\\n /// When the run started, in milliseconds since the Unix epoch.\\n ///\\n /// Milliseconds rather than the `.ts`'s ISO-8601 string, because a core has\\n /// no clock and no timezone database and should not grow a date parser to\\n /// avoid needing one. The host formats for display; the core does arithmetic.\\n pub started_at_ms: f64,\\n /// Where it ran from.\\n pub region: String,\\n /// The run's outcome.\\n pub status: ProbeStatus,\\n /// Total run duration.\\n pub duration_ms: f64,\\n /// Each step, in order.\\n pub steps: Vec<StepResult>,\\n}\\n\\nimpl ProbeResult {\\n /// Roll finished steps up into a run.\\n ///\\n /// The status is DERIVED, never passed in. A runner that reported a passing\\n /// run containing a failed step would be believed, and this is the one place\\n /// that can prevent it — so the invariant lives in a constructor rather than\\n /// in a rule somebody has to remember.\\n #[must_use]\\n pub fn from_steps(\\n journey_id: impl Into<String>,\\n region: impl Into<String>,\\n started_at_ms: f64,\\n duration_ms: f64,\\n steps: Vec<StepResult>,\\n ) -> Self {\\n let status = if steps.iter().any(|s| s.status == StepStatus::Fail) {\\n ProbeStatus::Fail\\n } else {\\n ProbeStatus::Pass\\n };\\n Self {\\n journey_id: journey_id.into(),\\n started_at_ms,\\n region: region.into(),\\n status,\\n duration_ms,\\n steps,\\n }\\n }\\n\\n /// How many steps failed.\\n #[must_use]\\n pub fn failed_step_count(&self) -> usize {\\n self.steps\\n .iter()\\n .filter(|s| s.status == StepStatus::Fail)\\n .count()\\n }\\n\\n /// The first failure's reason, for a summary line.\\n #[must_use]\\n pub fn first_failure(&self) -> Option<&StepResult> {\\n self.steps.iter().find(|s| s.status == StepStatus::Fail)\\n }\\n}\\n\\n/// Whether this run should raise an alert.\\n///\\n/// Pure, and separate from the run itself, because \\\"did it fail\\\" and \\\"should\\n/// somebody be woken\\\" are different questions. A journey that fails constantly in\\n/// a known-degraded region and one that fails for the first time in production\\n/// produce the same [`ProbeStatus`] and warrant different responses; keeping the\\n/// decision here means a host can change it without touching a runner.\\n#[must_use]\\npub fn should_alert(journey: &SyntheticJourney, result: &ProbeResult) -> bool {\\n journey.alert_on_failure && result.status.is_failure()\\n}\\n\\n/// Attributes describing a run, for whatever the host reports it through.\\n///\\n/// Lives here rather than in the exporter so that a host reporting to something\\n/// other than OTLP still gets the same facts under the same names.\\n#[must_use]\\npub fn run_attributes(result: &ProbeResult) -> BTreeMap<String, String> {\\n let mut attrs = BTreeMap::new();\\n attrs.insert(\\\"mzizi.probe.journey\\\".to_owned(), result.journey_id.clone());\\n attrs.insert(\\n \\\"mzizi.probe.status\\\".to_owned(),\\n result.status.as_str().to_owned(),\\n );\\n attrs.insert(\\\"mzizi.probe.region\\\".to_owned(), result.region.clone());\\n attrs\\n}\\n\\n// ── journey templates ──────────────────────────────────────────────────────\\n\\n/// The auth journey for a mini-app.\\n///\\n/// The credentials are the `.ts`'s, unchanged, and they are placeholders for a\\n/// seeded probe account rather than anything that opens a real door. Kept\\n/// identical so the two implementations exercise the same path; a host wanting\\n/// real credentials substitutes them, and should not be reading them from here.\\n#[must_use]\\npub fn auth_flow(mini_app: &str) -> SyntheticJourney {\\n SyntheticJourney {\\n id: format!(\\\"auth-{mini_app}\\\"),\\n name: format!(\\\"{mini_app} Auth Flow\\\"),\\n mini_app: Some(mini_app.to_owned()),\\n nodes: vec![6, 4, 7],\\n steps: vec![\\n step(\\n ProbeStepType::Navigate,\\n Some(\\\"/login\\\"),\\n None,\\n \\\"Navigate to login\\\",\\n ),\\n step(\\n ProbeStepType::Input,\\n Some(\\\"[name=email]\\\"),\\n Some(\\\"probe@nyuchi.com\\\"),\\n \\\"Enter email\\\",\\n ),\\n step(\\n ProbeStepType::Input,\\n Some(\\\"[name=password]\\\"),\\n Some(\\\"probe-pass\\\"),\\n \\\"Enter password\\\",\\n ),\\n step(\\n ProbeStepType::Click,\\n Some(\\\"[type=submit]\\\"),\\n None,\\n \\\"Submit login\\\",\\n ),\\n step(\\n ProbeStepType::Assert,\\n Some(\\\"[data-slot=mzizi-header]\\\"),\\n None,\\n \\\"Verify header renders\\\",\\n ),\\n ],\\n schedule: None,\\n regions: Vec::new(),\\n alert_on_failure: true,\\n }\\n}\\n\\n/// The wallet-balance journey.\\n#[must_use]\\npub fn wallet_flow() -> SyntheticJourney {\\n SyntheticJourney {\\n id: \\\"wallet-balance\\\".to_owned(),\\n name: \\\"Wallet Balance Check\\\".to_owned(),\\n mini_app: Some(\\\"wallet\\\".to_owned()),\\n nodes: vec![6, 4, 3, 2],\\n steps: vec![\\n step(\\n ProbeStepType::Navigate,\\n Some(\\\"/wallet\\\"),\\n None,\\n \\\"Navigate to wallet\\\",\\n ),\\n step(\\n ProbeStepType::Assert,\\n Some(\\\"[data-slot=wallet-page]\\\"),\\n None,\\n \\\"Verify wallet page renders\\\",\\n ),\\n step(\\n ProbeStepType::Assert,\\n Some(\\\"[data-slot=kpi-card]\\\"),\\n None,\\n \\\"Verify balance card renders\\\",\\n ),\\n ],\\n schedule: None,\\n regions: Vec::new(),\\n alert_on_failure: true,\\n }\\n}\\n\\nfn step(\\n step_type: ProbeStepType,\\n target: Option<&str>,\\n value: Option<&str>,\\n description: &str,\\n) -> ProbeStep {\\n ProbeStep {\\n step_type,\\n target: target.map(ToOwned::to_owned),\\n value: value.map(ToOwned::to_owned),\\n timeout_ms: None,\\n description: description.to_owned(),\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1236
|
+
"mzizi-theme-provider": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-theme-provider\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Theme provider component that wraps the application and distributes design tokens via CSS custom properties and React context. Supports light/dark/high-contrast themes, per-brand accent overrides, system preference detection (prefers-color-scheme, prefers-contrast), and runtime theme switching. Every Nyuchi app wraps its root in this provider.\",\"crate\":{\"name\":\"mzizi-shell\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n7-shell/mzizi-theme-provider.rs\",\"type\":\"registry:rust\",\"content\":\"//! NYUCHI THEME PROVIDER — N7 shell, Dioxus.\\n//!\\n//! The Rust sibling of `mzizi-theme-provider.tsx`'s decision logic: which theme wins at\\n//! startup, and how system preferences resolve to a mode.\\n//!\\n//! System-preference detection (`matchMedia`), persistence (`localStorage`), and\\n//! `document.documentElement` class/attribute wiring are all real-DOM glue with no portable\\n//! pure logic behind them — the host owns that part and passes in the already-resolved values,\\n//! the same way this node's other components take a host-controlled `visible` /\\n//! `prefers_reduced_motion` prop instead of re-implementing browser-only mechanisms in Rust.\\n//! [`MziziThemeProvider`] is accordingly a thin wrapper: it injects the host-generated CSS\\n//! variables and renders children; [`resolve_initial_theme`] and [`system_preference`] are the\\n//! parts worth testing.\\n\\nuse dioxus::prelude::*;\\n\\n/// The resolved theme mode.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\\npub enum ThemeMode {\\n /// Light mode.\\n Light,\\n /// Dark mode.\\n Dark,\\n /// High-contrast mode, overriding light/dark.\\n HighContrast,\\n}\\n\\n/// System-preference resolution. Contrast wins over dark, matching the\\n/// `.tsx`: `contrastMq.matches ? \\\"high-contrast\\\" : darkMq.matches ? \\\"dark\\\" : \\\"light\\\"`.\\npub fn system_preference(prefers_high_contrast: bool, prefers_dark: bool) -> ThemeMode {\\n if prefers_high_contrast {\\n ThemeMode::HighContrast\\n } else if prefers_dark {\\n ThemeMode::Dark\\n } else {\\n ThemeMode::Light\\n }\\n}\\n\\n/// Initial theme priority: a stored (persisted) choice wins outright;\\n/// otherwise the system preference is used only if the host opted in;\\n/// otherwise the configured default. Mirrors the `.tsx`'s\\n/// `useState(() => stored ?? (useSystemPreference ? systemPreference : defaultTheme))`.\\npub fn resolve_initial_theme(\\n stored: Option<ThemeMode>,\\n use_system_preference: bool,\\n system_preference: ThemeMode,\\n default_theme: ThemeMode,\\n) -> ThemeMode {\\n if let Some(theme) = stored {\\n return theme;\\n }\\n if use_system_preference {\\n system_preference\\n } else {\\n default_theme\\n }\\n}\\n\\n/// Props for [`MziziThemeProvider`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziThemeProviderProps {\\n /// The resolved theme mode, already decided by the host.\\n pub theme: ThemeMode,\\n /// The active brand identity.\\n pub brand: String,\\n /// Host-generated CSS custom properties, injected verbatim as a `<style>` tag.\\n pub css_vars: String,\\n /// The subtree this provider wraps.\\n pub children: Element,\\n}\\n\\n/// Injects the host-resolved theme's CSS variables and renders children.\\n#[component]\\npub fn MziziThemeProvider(props: MziziThemeProviderProps) -> Element {\\n rsx! {\\n style { dangerous_inner_html: \\\"{props.css_vars}\\\" }\\n {props.children.clone()}\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn high_contrast_wins_over_dark() {\\n assert_eq!(system_preference(true, true), ThemeMode::HighContrast);\\n }\\n\\n #[test]\\n fn dark_wins_over_light_when_no_contrast() {\\n assert_eq!(system_preference(false, true), ThemeMode::Dark);\\n }\\n\\n #[test]\\n fn light_is_the_fallback() {\\n assert_eq!(system_preference(false, false), ThemeMode::Light);\\n }\\n\\n #[test]\\n fn stored_choice_wins_over_everything() {\\n let resolved = resolve_initial_theme(\\n Some(ThemeMode::Light),\\n true,\\n ThemeMode::Dark,\\n ThemeMode::Dark,\\n );\\n assert_eq!(resolved, ThemeMode::Light);\\n }\\n\\n #[test]\\n fn system_preference_used_when_opted_in_and_nothing_stored() {\\n let resolved = resolve_initial_theme(None, true, ThemeMode::HighContrast, ThemeMode::Dark);\\n assert_eq!(resolved, ThemeMode::HighContrast);\\n }\\n\\n #[test]\\n fn default_theme_used_when_not_using_system_preference() {\\n let resolved = resolve_initial_theme(None, false, ThemeMode::HighContrast, ThemeMode::Dark);\\n assert_eq!(resolved, ThemeMode::Dark);\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1237
|
+
"mzizi-toast-provider": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-toast-provider\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"App-level toast/notification stack. Manages positioning, stacking, auto-dismiss, and swipe-to-dismiss for transient notifications. Wraps the L2 toast primitive with app-level behavior.\",\"crate\":{\"name\":\"mzizi-shell\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n7-shell/mzizi-toast-provider.rs\",\"type\":\"registry:rust\",\"content\":\"//! NYUCHI TOAST PROVIDER — N7 shell, Dioxus.\\n//!\\n//! The Rust sibling of `mzizi-toast-provider.tsx`: a toast queue with position and cap,\\n//! sharing its contract — same `data-slot`, same position/type class maps.\\n//!\\n//! # `max_visible == 0` is a `.tsx` edge case\\n//!\\n//! The `.tsx` caps the queue with `[...prev.slice(-(maxVisible - 1)), item]`. At\\n//! `maxVisible === 0` that becomes `prev.slice(-(-1))`, i.e. `prev.slice(1)` — which drops only\\n//! the oldest existing toast and still appends the new one, so a \\\"cap of zero\\\" would still show\\n//! one toast. [`push_toast`] treats `max_visible == 0` as \\\"show none\\\" instead.\\n//!\\n//! **The `.tsx` sibling still has this.**\\n//!\\n//! Toast IDs are the caller's responsibility here rather than generated internally\\n//! (`Date.now().toString(36) + Math.random()...` in the `.tsx`) — pushing ID generation to the\\n//! host keeps the queue logic pure and deterministic to test.\\n\\nuse dioxus::prelude::*;\\n\\n/// Visual/semantic category of a toast.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\\npub enum ToastKind {\\n /// No particular semantic category.\\n Default,\\n /// A positive/confirming outcome.\\n Success,\\n /// A failure.\\n Error,\\n /// A caution.\\n Warning,\\n /// A neutral notice.\\n Info,\\n}\\n\\n/// Where the toast stack is anchored on screen.\\n#[derive(Clone, Copy, Debug, PartialEq, Eq)]\\npub enum ToastPosition {\\n /// Anchored to the top-right corner.\\n TopRight,\\n /// Anchored to top-center.\\n TopCenter,\\n /// Anchored to the bottom-right corner, above the bottom nav.\\n BottomRight,\\n /// Anchored to bottom-center, above the bottom nav.\\n BottomCenter,\\n}\\n\\n/// Tailwind position classes for a [`ToastPosition`].\\npub fn position_class(position: ToastPosition) -> &'static str {\\n match position {\\n ToastPosition::TopRight => \\\"top-4 right-4\\\",\\n ToastPosition::TopCenter => \\\"top-4 left-1/2 -translate-x-1/2\\\",\\n ToastPosition::BottomRight => \\\"bottom-20 right-4\\\",\\n ToastPosition::BottomCenter => \\\"bottom-20 left-1/2 -translate-x-1/2\\\",\\n }\\n}\\n\\n/// Tailwind accent classes for a [`ToastKind`].\\npub fn type_style_class(kind: ToastKind) -> &'static str {\\n match kind {\\n ToastKind::Default => \\\"border-border\\\",\\n ToastKind::Success => \\\"border-l-4 border-l-[var(--status-success,#64FFDA)]\\\",\\n ToastKind::Error => \\\"border-l-4 border-l-[var(--status-error,#FF5252)]\\\",\\n ToastKind::Warning => \\\"border-l-4 border-l-[var(--status-warning,#FFD740)]\\\",\\n ToastKind::Info => \\\"border-l-4 border-l-[var(--status-info,#00B0FF)]\\\",\\n }\\n}\\n\\n/// A single toast in the queue.\\n#[derive(Clone, Debug, PartialEq)]\\npub struct ToastItem {\\n /// Unique identifier, supplied by the caller.\\n pub id: String,\\n /// Visual/semantic category.\\n pub kind: ToastKind,\\n /// Headline text.\\n pub title: String,\\n /// Optional body text.\\n pub message: Option<String>,\\n /// Whether the user can dismiss this toast manually.\\n pub dismissible: bool,\\n}\\n\\n/// Appends a new toast, keeping the queue capped at `max_visible` — see the module docs for how\\n/// this differs from the `.tsx` at `max_visible == 0`.\\npub fn push_toast(existing: &[ToastItem], item: ToastItem, max_visible: usize) -> Vec<ToastItem> {\\n if max_visible == 0 {\\n return Vec::new();\\n }\\n let keep = max_visible.saturating_sub(1);\\n let start = existing.len().saturating_sub(keep);\\n let mut next: Vec<ToastItem> = existing[start..].to_vec();\\n next.push(item);\\n next\\n}\\n\\n/// Removes a toast by id.\\npub fn dismiss(existing: &[ToastItem], id: &str) -> Vec<ToastItem> {\\n existing.iter().filter(|t| t.id != id).cloned().collect()\\n}\\n\\n/// Props for [`MziziToastProvider`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziToastProviderProps {\\n /// Where the toast stack is anchored.\\n #[props(default = ToastPosition::BottomRight)]\\n pub position: ToastPosition,\\n /// The current toast queue, controlled by the host.\\n pub toasts: Vec<ToastItem>,\\n /// Fired with a toast's `id` when it is dismissed.\\n #[props(default)]\\n pub on_dismiss: EventHandler<String>,\\n /// The subtree this provider wraps.\\n pub children: Element,\\n}\\n\\n/// Renders children plus a positioned toast stack.\\n#[component]\\npub fn MziziToastProvider(props: MziziToastProviderProps) -> Element {\\n rsx! {\\n {props.children.clone()}\\n div {\\n \\\"data-slot\\\": \\\"mzizi-toast-provider\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-toast-provider\\\",\\n \\\"aria-live\\\": \\\"polite\\\",\\n \\\"aria-atomic\\\": \\\"false\\\",\\n class: \\\"pointer-events-none fixed z-50 flex w-full max-w-sm flex-col gap-2 {position_class(props.position)}\\\",\\n for toast in props.toasts.iter() {\\n div {\\n key: \\\"{toast.id}\\\",\\n role: \\\"alert\\\",\\n class: \\\"pointer-events-auto rounded-[var(--radius-lg,14px)] border bg-card p-4 shadow-lg {type_style_class(toast.kind)}\\\",\\n div { class: \\\"flex items-start justify-between gap-2\\\",\\n div { class: \\\"min-w-0\\\",\\n p { class: \\\"text-sm font-medium\\\", \\\"{toast.title}\\\" }\\n if let Some(message) = &toast.message {\\n p { class: \\\"mt-0.5 text-xs text-muted-foreground\\\", \\\"{message}\\\" }\\n }\\n }\\n if toast.dismissible {\\n button {\\n \\\"aria-label\\\": \\\"Dismiss\\\",\\n class: \\\"flex min-h-[48px] min-w-[48px] shrink-0 items-center justify-center text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\\\",\\n onclick: {\\n let id = toast.id.clone();\\n move |_| props.on_dismiss.call(id.clone())\\n },\\n \\\"\\\\u{2715}\\\"\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n fn toast(id: &str) -> ToastItem {\\n ToastItem {\\n id: id.into(),\\n kind: ToastKind::Default,\\n title: id.into(),\\n message: None,\\n dismissible: true,\\n }\\n }\\n\\n #[test]\\n fn queue_is_capped_at_max_visible() {\\n let existing = vec![toast(\\\"a\\\"), toast(\\\"b\\\"), toast(\\\"c\\\")];\\n let next = push_toast(&existing, toast(\\\"d\\\"), 3);\\n assert_eq!(next.len(), 3);\\n assert_eq!(\\n next.iter().map(|t| t.id.as_str()).collect::<Vec<_>>(),\\n vec![\\\"b\\\", \\\"c\\\", \\\"d\\\"]\\n );\\n }\\n\\n #[test]\\n fn queue_grows_until_the_cap_is_reached() {\\n let existing = vec![toast(\\\"a\\\")];\\n let next = push_toast(&existing, toast(\\\"b\\\"), 3);\\n assert_eq!(\\n next.iter().map(|t| t.id.as_str()).collect::<Vec<_>>(),\\n vec![\\\"a\\\", \\\"b\\\"]\\n );\\n }\\n\\n #[test]\\n fn max_visible_zero_shows_nothing() {\\n // The .tsx's `prev.slice(-(maxVisible - 1))` degrades to\\n // `slice(1)` when maxVisible is 0 — it drops the oldest toast and\\n // still appends the new one, so \\\"cap of zero\\\" would still show\\n // one toast. Fixed here to show none.\\n let existing = vec![toast(\\\"a\\\")];\\n let next = push_toast(&existing, toast(\\\"b\\\"), 0);\\n assert!(next.is_empty());\\n }\\n\\n #[test]\\n fn dismiss_removes_only_the_matching_id() {\\n let existing = vec![toast(\\\"a\\\"), toast(\\\"b\\\")];\\n let next = dismiss(&existing, \\\"a\\\");\\n assert_eq!(\\n next.iter().map(|t| t.id.as_str()).collect::<Vec<_>>(),\\n vec![\\\"b\\\"]\\n );\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1238
|
+
"mzizi-tokens-rust": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-tokens-rust\",\"type\":\"registry:lib\",\"target\":\"dioxus\",\"description\":\"Mzizi design tokens for Rust. Generated by `pnpm tokens:sync` from the same source that emits the web CSS custom properties — seven African Minerals + seven Heritage tones, both themes, as `const` values plus `Palette`, `Spacing`, `Radius` and `Fonts`. This is what the Dioxus half of the registry consumes, via the `mzizi-tokens` crate. Never hand-edited: adding a target means adding an emitter, never re-authoring the palette (CLAUDE.md §8.4.1).\",\"crate\":{\"name\":\"mzizi-tokens\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n1-tokens/mzizi-tokens-rust.rs\",\"type\":\"registry:rust\",\"content\":\"// NYUCHI DESIGN TOKENS — N1\\n// Rust — generated by scripts/sync-tokens.ts from\\n// lib/tokens/palette.source.ts, the canonical palette in this repo.\\n//\\n// Carries all 21 families: seven minerals, seven heritage tones and\\n// the seven experimental — the same set /v1/brand and the MCP serve.\\n//\\n// DO NOT EDIT BY HAND. Run `pnpm tokens:sync`; `pnpm tokens:verify`\\n// fails the build if this file drifts from the source.\\n//\\n// This is what the Dioxus half of the registry consumes, and what\\n// `mzizi-rs/crates/mzizi-tokens` includes by path so the crates registry\\n// publishes the same palette the MCP and the shadcn registry do.\\n\\n#![allow(dead_code)]\\n\\n// ─── Seven African Minerals ─────────────────────────────────────────────────\\n/// Cobalt — dark theme.\\npub const COBALT_DARK: &str = \\\"#00B0FF\\\";\\n/// Cobalt — light theme.\\npub const COBALT_LIGHT: &str = \\\"#0047AB\\\";\\n/// Tanzanite — dark theme.\\npub const TANZANITE_DARK: &str = \\\"#B388FF\\\";\\n/// Tanzanite — light theme.\\npub const TANZANITE_LIGHT: &str = \\\"#4B0082\\\";\\n/// Malachite — dark theme.\\npub const MALACHITE_DARK: &str = \\\"#64FFDA\\\";\\n/// Malachite — light theme.\\npub const MALACHITE_LIGHT: &str = \\\"#004D40\\\";\\n/// Gold — dark theme.\\npub const GOLD_DARK: &str = \\\"#FFD740\\\";\\n/// Gold — light theme.\\npub const GOLD_LIGHT: &str = \\\"#5D4037\\\";\\n/// Terracotta — dark theme.\\npub const TERRACOTTA_DARK: &str = \\\"#E1B07E\\\";\\n/// Terracotta — light theme.\\npub const TERRACOTTA_LIGHT: &str = \\\"#A0522D\\\";\\n/// Sodalite — dark theme.\\npub const SODALITE_DARK: &str = \\\"#3D5AFE\\\";\\n/// Sodalite — light theme.\\npub const SODALITE_LIGHT: &str = \\\"#283593\\\";\\n/// Copper — dark theme.\\npub const COPPER_DARK: &str = \\\"#FF8A65\\\";\\n/// Copper — light theme.\\npub const COPPER_LIGHT: &str = \\\"#BF5A36\\\";\\n\\n// ─── Seven Heritage tones ───────────────────────────────────────────────────\\n/// Indigo — dark theme.\\npub const INDIGO_DARK: &str = \\\"#7986CB\\\";\\n/// Indigo — light theme.\\npub const INDIGO_LIGHT: &str = \\\"#4527A0\\\";\\n/// Savanna — dark theme.\\npub const SAVANNA_DARK: &str = \\\"#E5C158\\\";\\n/// Savanna — light theme.\\npub const SAVANNA_LIGHT: &str = \\\"#8D6E1A\\\";\\n/// Baobab — dark theme.\\npub const BAOBAB_DARK: &str = \\\"#A1887F\\\";\\n/// Baobab — light theme.\\npub const BAOBAB_LIGHT: &str = \\\"#4E342E\\\";\\n/// Sunset — dark theme.\\npub const SUNSET_DARK: &str = \\\"#FF7043\\\";\\n/// Sunset — light theme.\\npub const SUNSET_LIGHT: &str = \\\"#D84315\\\";\\n/// River — dark theme.\\npub const RIVER_DARK: &str = \\\"#4DD0E1\\\";\\n/// River — light theme.\\npub const RIVER_LIGHT: &str = \\\"#006064\\\";\\n/// Hematite — dark theme.\\npub const HEMATITE_DARK: &str = \\\"#90A4AE\\\";\\n/// Hematite — light theme.\\npub const HEMATITE_LIGHT: &str = \\\"#546E7A\\\";\\n/// Kalahari — dark theme.\\npub const KALAHARI_DARK: &str = \\\"#E8D9B5\\\";\\n/// Kalahari — light theme.\\npub const KALAHARI_LIGHT: &str = \\\"#C9B589\\\";\\n\\n// ─── Seven Experimental tones ───────────────────────────────────────────────\\n/// Ember — dark theme.\\npub const EMBER_DARK: &str = \\\"#DA8766\\\";\\n/// Ember — light theme.\\npub const EMBER_LIGHT: &str = \\\"#843D20\\\";\\n/// Acacia — dark theme.\\npub const ACACIA_DARK: &str = \\\"#93A528\\\";\\n/// Acacia — light theme.\\npub const ACACIA_LIGHT: &str = \\\"#4D5615\\\";\\n/// Fern — dark theme.\\npub const FERN_DARK: &str = \\\"#2CB42B\\\";\\n/// Fern — light theme.\\npub const FERN_LIGHT: &str = \\\"#175E17\\\";\\n/// Lagoon — dark theme.\\npub const LAGOON_DARK: &str = \\\"#2AAE9B\\\";\\n/// Lagoon — light theme.\\npub const LAGOON_LIGHT: &str = \\\"#165B51\\\";\\n/// Storm — dark theme.\\npub const STORM_DARK: &str = \\\"#7E9BE0\\\";\\n/// Storm — light theme.\\npub const STORM_LIGHT: &str = \\\"#284CA6\\\";\\n/// Dusk — dark theme.\\npub const DUSK_DARK: &str = \\\"#BA87E2\\\";\\n/// Dusk — light theme.\\npub const DUSK_LIGHT: &str = \\\"#742AAD\\\";\\n/// Protea — dark theme.\\npub const PROTEA_DARK: &str = \\\"#DF7BB4\\\";\\n/// Protea — light theme.\\npub const PROTEA_LIGHT: &str = \\\"#932464\\\";\\n\\n/// Every palette colour for one theme. Construct with [`Palette::dark`] or\\n/// [`Palette::light`] rather than by hand, so a new colour cannot be missed.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\npub struct Palette {\\n /// Cobalt, resolved for this theme.\\n pub cobalt: &'static str,\\n /// Tanzanite, resolved for this theme.\\n pub tanzanite: &'static str,\\n /// Malachite, resolved for this theme.\\n pub malachite: &'static str,\\n /// Gold, resolved for this theme.\\n pub gold: &'static str,\\n /// Terracotta, resolved for this theme.\\n pub terracotta: &'static str,\\n /// Sodalite, resolved for this theme.\\n pub sodalite: &'static str,\\n /// Copper, resolved for this theme.\\n pub copper: &'static str,\\n /// Indigo, resolved for this theme.\\n pub indigo: &'static str,\\n /// Savanna, resolved for this theme.\\n pub savanna: &'static str,\\n /// Baobab, resolved for this theme.\\n pub baobab: &'static str,\\n /// Sunset, resolved for this theme.\\n pub sunset: &'static str,\\n /// River, resolved for this theme.\\n pub river: &'static str,\\n /// Hematite, resolved for this theme.\\n pub hematite: &'static str,\\n /// Kalahari, resolved for this theme.\\n pub kalahari: &'static str,\\n /// Ember, resolved for this theme.\\n pub ember: &'static str,\\n /// Acacia, resolved for this theme.\\n pub acacia: &'static str,\\n /// Fern, resolved for this theme.\\n pub fern: &'static str,\\n /// Lagoon, resolved for this theme.\\n pub lagoon: &'static str,\\n /// Storm, resolved for this theme.\\n pub storm: &'static str,\\n /// Dusk, resolved for this theme.\\n pub dusk: &'static str,\\n /// Protea, resolved for this theme.\\n pub protea: &'static str,\\n}\\n\\nimpl Palette {\\n /// The palette resolved for the dark theme.\\n pub const fn dark() -> Self {\\n Self {\\n cobalt: COBALT_DARK,\\n tanzanite: TANZANITE_DARK,\\n malachite: MALACHITE_DARK,\\n gold: GOLD_DARK,\\n terracotta: TERRACOTTA_DARK,\\n sodalite: SODALITE_DARK,\\n copper: COPPER_DARK,\\n indigo: INDIGO_DARK,\\n savanna: SAVANNA_DARK,\\n baobab: BAOBAB_DARK,\\n sunset: SUNSET_DARK,\\n river: RIVER_DARK,\\n hematite: HEMATITE_DARK,\\n kalahari: KALAHARI_DARK,\\n ember: EMBER_DARK,\\n acacia: ACACIA_DARK,\\n fern: FERN_DARK,\\n lagoon: LAGOON_DARK,\\n storm: STORM_DARK,\\n dusk: DUSK_DARK,\\n protea: PROTEA_DARK,\\n }\\n }\\n\\n /// The palette resolved for the light theme.\\n pub const fn light() -> Self {\\n Self {\\n cobalt: COBALT_LIGHT,\\n tanzanite: TANZANITE_LIGHT,\\n malachite: MALACHITE_LIGHT,\\n gold: GOLD_LIGHT,\\n terracotta: TERRACOTTA_LIGHT,\\n sodalite: SODALITE_LIGHT,\\n copper: COPPER_LIGHT,\\n indigo: INDIGO_LIGHT,\\n savanna: SAVANNA_LIGHT,\\n baobab: BAOBAB_LIGHT,\\n sunset: SUNSET_LIGHT,\\n river: RIVER_LIGHT,\\n hematite: HEMATITE_LIGHT,\\n kalahari: KALAHARI_LIGHT,\\n ember: EMBER_LIGHT,\\n acacia: ACACIA_LIGHT,\\n fern: FERN_LIGHT,\\n lagoon: LAGOON_LIGHT,\\n storm: STORM_LIGHT,\\n dusk: DUSK_LIGHT,\\n protea: PROTEA_LIGHT,\\n }\\n }\\n}\\n\\n/// Spacing scale, in pixels.\\npub struct Spacing;\\n\\nimpl Spacing {\\n /// 4px.\\n pub const XS: u32 = 4;\\n /// 8px.\\n pub const SM: u32 = 8;\\n /// 12px.\\n pub const MD: u32 = 12;\\n /// 16px.\\n pub const BASE: u32 = 16;\\n /// 24px.\\n pub const LG: u32 = 24;\\n /// 32px.\\n pub const XL: u32 = 32;\\n}\\n\\n/// Radius scale, in pixels. Every value derives from the 7px unit.\\npub struct Radius;\\n\\nimpl Radius {\\n /// 7px.\\n pub const SM: u32 = 7;\\n /// 12px.\\n pub const MD: u32 = 12;\\n /// 14px.\\n pub const LG: u32 = 14;\\n /// 17px.\\n pub const XL: u32 = 17;\\n /// 9999px.\\n pub const FULL: u32 = 9999;\\n}\\n\\n/// The canonical type stack.\\npub struct Fonts;\\n\\nimpl Fonts {\\n /// Noto Sans.\\n pub const SANS: &'static str = \\\"Noto Sans\\\";\\n /// Noto Serif.\\n pub const SERIF: &'static str = \\\"Noto Serif\\\";\\n /// JetBrains Mono.\\n pub const MONO: &'static str = \\\"JetBrains Mono\\\";\\n}\\n\"}],\"contract\":null}",
|
|
1239
|
+
"mzizi-update-prompt": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-update-prompt\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"App update / new version available prompt. Shows when a new version is detected via service worker. Dismissible with optional force-update for critical patches.\",\"crate\":{\"name\":\"mzizi-shell\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n7-shell/mzizi-update-prompt.rs\",\"type\":\"registry:rust\",\"content\":\"//! NYUCHI UPDATE PROMPT — N7 shell, Dioxus.\\n//!\\n//! The Rust sibling of `mzizi-update-prompt.tsx`: a bottom-sheet asking the user to refresh\\n//! for a new version, sharing its contract — same `data-slot`, same critical/optional split.\\n//!\\n//! # The harness's motion preference is a parameter, not a dependency\\n//!\\n//! The `.tsx` reads `motion.prefersReduced` and `motion.enterDuration`/`enterEasing` from\\n//! `useMziziHarness`, which is an N3 component (`mzizi-harness.tsx`) with no Rust port yet.\\n//! [`UpdatePromptProps::prefers_reduced_motion`] takes the one bit this component actually\\n//! branches on directly; a host with the full harness passes its `motion.prefersReduced`\\n//! through, and a host without one can pass `false` and get the entrance animation.\\n\\nuse dioxus::prelude::*;\\n\\nconst ROOT: &str = \\\"fixed right-4 bottom-20 left-4 z-50 mx-auto max-w-sm rounded-[var(--radius-xl,17px)] border border-border bg-card p-4 shadow-2xl\\\";\\nconst UPDATE_BUTTON: &str = \\\"min-h-[48px] flex-1 rounded-full bg-primary text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\\\";\\nconst DISMISS_BUTTON: &str = \\\"min-h-[48px] rounded-full bg-muted px-4 text-sm font-medium text-foreground transition-colors hover:bg-muted/80\\\";\\n\\n/// Join a base class string with a consumer's extra classes.\\nfn join(base: &str, extra: &str) -> String {\\n if extra.is_empty() {\\n base.to_owned()\\n } else {\\n format!(\\\"{base} {extra}\\\")\\n }\\n}\\n\\n/// The body copy, matching the `.tsx` exactly including its conditional phrasing.\\n#[must_use]\\npub fn body_text(version: Option<&str>, is_critical: bool) -> String {\\n let lead = version.map_or_else(\\n || \\\"A new version is ready.\\\".to_owned(),\\n |v| format!(\\\"Version {v} is ready.\\\"),\\n );\\n let tail = if is_critical {\\n \\\"This update is required to continue.\\\"\\n } else {\\n \\\"Refresh to get the latest improvements.\\\"\\n };\\n format!(\\\"{lead} {tail}\\\")\\n}\\n\\n/// The animation inline style, matching the `.tsx`'s `motion.prefersReduced` branch.\\n///\\n/// Empty when motion is reduced, matching the `.tsx`'s `{}`.\\n#[must_use]\\npub fn entrance_style(prefers_reduced_motion: bool, duration_ms: u32, easing: &str) -> String {\\n if prefers_reduced_motion {\\n String::new()\\n } else {\\n format!(\\\"animation: nyuchi-fade-slide-up {duration_ms}ms {easing} both;\\\")\\n }\\n}\\n\\n/// Props for [`UpdatePrompt`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct UpdatePromptProps {\\n /// Whether the prompt is shown.\\n #[props(default = false)]\\n pub visible: bool,\\n /// Version being offered.\\n #[props(default)]\\n pub version: Option<String>,\\n /// Whether declining is not an option — hides the dismiss button.\\n #[props(default = false)]\\n pub is_critical: bool,\\n /// Called when the user chooses to update.\\n #[props(default)]\\n pub on_update: Option<EventHandler<()>>,\\n /// Called when the user dismisses. Ignored when `is_critical` is true, matching the\\n /// `.tsx`'s `{!isCritical && onDismiss && ...}`.\\n #[props(default)]\\n pub on_dismiss: Option<EventHandler<()>>,\\n /// See the module docs.\\n #[props(default = false)]\\n pub prefers_reduced_motion: bool,\\n /// Entrance animation duration in ms, when motion is not reduced.\\n #[props(default = 220)]\\n pub enter_duration_ms: u32,\\n /// Entrance animation easing, when motion is not reduced.\\n #[props(default = \\\"ease-out\\\".to_owned())]\\n pub enter_easing: String,\\n /// Extra classes, merged onto the root.\\n #[props(default)]\\n pub class: String,\\n /// Any other root attributes.\\n #[props(extends = GlobalAttributes)]\\n pub attributes: Vec<Attribute>,\\n}\\n\\n/// A bottom-sheet prompting the user to refresh for a new version.\\n#[component]\\npub fn UpdatePrompt(props: UpdatePromptProps) -> Element {\\n if !props.visible {\\n return rsx! {};\\n }\\n\\n let heading = if props.is_critical {\\n \\\"Critical update required\\\"\\n } else {\\n \\\"Update available\\\"\\n };\\n let body = body_text(props.version.as_deref(), props.is_critical);\\n let style = entrance_style(\\n props.prefers_reduced_motion,\\n props.enter_duration_ms,\\n &props.enter_easing,\\n );\\n let show_dismiss = !props.is_critical && props.on_dismiss.is_some();\\n\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"mzizi-update-prompt\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/mzizi-update-prompt\\\",\\n role: \\\"alertdialog\\\",\\n \\\"aria-label\\\": \\\"Update available\\\",\\n style: \\\"{style}\\\",\\n class: join(ROOT, &props.class),\\n ..props.attributes,\\n\\n p { class: \\\"text-sm font-semibold\\\", \\\"{heading}\\\" }\\n p { class: \\\"mt-1 text-xs text-muted-foreground\\\", \\\"{body}\\\" }\\n div { class: \\\"mt-3 flex gap-2\\\",\\n button {\\n class: UPDATE_BUTTON,\\n onclick: move |_| {\\n if let Some(h) = props.on_update { h.call(()) }\\n },\\n \\\"Update\\\"\\n }\\n if show_dismiss {\\n button {\\n class: DISMISS_BUTTON,\\n onclick: move |_| {\\n if let Some(h) = props.on_dismiss { h.call(()) }\\n },\\n \\\"Later\\\"\\n }\\n }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn body_text_with_version_and_critical() {\\n assert_eq!(\\n body_text(Some(\\\"4.2.0\\\"), true),\\n \\\"Version 4.2.0 is ready. This update is required to continue.\\\"\\n );\\n }\\n\\n #[test]\\n fn body_text_without_version_and_optional() {\\n assert_eq!(\\n body_text(None, false),\\n \\\"A new version is ready. Refresh to get the latest improvements.\\\"\\n );\\n }\\n\\n #[test]\\n fn reduced_motion_produces_no_animation_style() {\\n assert_eq!(entrance_style(true, 220, \\\"ease-out\\\"), \\\"\\\");\\n }\\n\\n #[test]\\n fn full_motion_names_the_fade_slide_up_keyframe() {\\n let style = entrance_style(false, 300, \\\"ease-in-out\\\");\\n assert!(style.contains(\\\"nyuchi-fade-slide-up\\\"));\\n assert!(style.contains(\\\"300ms\\\"));\\n assert!(style.contains(\\\"ease-in-out\\\"));\\n }\\n\\n #[test]\\n fn the_update_button_meets_the_touch_floor() {\\n assert!(UPDATE_BUTTON.contains(\\\"min-h-[48px]\\\"));\\n assert!(DISMISS_BUTTON.contains(\\\"min-h-[48px]\\\"));\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1240
|
+
"mzizi-user-card": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"mzizi-user-card\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Compact user profile card with avatar, name, role, and actions.\",\"crate\":{\"name\":\"mzizi-brand\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n3-brand/mzizi-user-card.rs\",\"type\":\"registry:rust\",\"content\":\"//! MZIZI USER CARD — N3 brand, Dioxus. A Mzizi Roots component.\\n//!\\n//! The Rust sibling of `mzizi-user-card.tsx`: a person with an avatar or initials, a name, an\\n//! optional role pill, an email line and trailing actions. Same `data-slot` (`user-card`, as\\n//! the `.tsx` has it), same `role=\\\"article\\\"`, same loading skeleton.\\n//!\\n//! # One skeleton, not two\\n//!\\n//! The `.tsx` has two `if (loading)` branches back to back; the second is unreachable. This\\n//! port has the first one only, which is what the `.tsx` renders.\\n//!\\n//! # Initials never split a code point\\n//!\\n//! The `.tsx` takes `n[0]` of each space-separated word, a UTF-16 code unit. [`initials`]\\n//! takes the first `char` and skips the empty words a double space produces.\\n//!\\n//! # The harness's motion preference is a prop\\n//!\\n//! [`MziziUserCardProps::prefers_reduced_motion`] replaces `useMziziHarness`'s\\n//! `motion.prefersReduced`, as `mzizi-update-prompt.rs` does in N7.\\n\\nuse dioxus::prelude::*;\\n\\n/// The component's contract, in the clause grammar of the Mzizi language's `contract … end`\\n/// block (mzizi-dev/mzizi `design/RFC-0006-contracts.md`). `mzizi-brand`'s contract suite\\n/// evaluates every clause against this component's server-rendered markup and fails on any\\n/// clause it cannot evaluate.\\npub const CONTRACT: &str = r#\\\"contract\\n slot is \\\"user-card\\\"\\n role is \\\"article\\\"\\n class contains \\\"rounded-2xl\\\"\\nend\\\"#;\\n\\n/// Up to two uppercase initials from the leading words of a name.\\n#[must_use]\\npub fn initials(name: &str) -> String {\\n name.split(' ')\\n .filter_map(|word| word.chars().next())\\n .take(2)\\n .flat_map(char::to_uppercase)\\n .collect()\\n}\\n\\n/// The entrance animation, empty when motion is reduced — the `.tsx`'s `animStyle`, with the\\n/// harness's 200ms decelerate curve.\\n#[must_use]\\npub fn entrance_style(prefers_reduced_motion: bool) -> &'static str {\\n if prefers_reduced_motion {\\n \\\"\\\"\\n } else {\\n \\\"animation: nyuchi-fade-slide-up 200ms cubic-bezier(0, 0, 0.2, 1) both;\\\"\\n }\\n}\\n\\nconst ROOT: &str =\\n \\\"flex items-center gap-4 rounded-2xl bg-card p-4 text-sm ring-1 ring-foreground/10\\\";\\n\\n/// Props for [`MziziUserCard`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct MziziUserCardProps {\\n /// Renders the skeleton instead of the card.\\n #[props(default)]\\n pub loading: bool,\\n /// Display name.\\n #[props(into)]\\n pub name: String,\\n /// Email line.\\n #[props(default)]\\n pub email: Option<String>,\\n /// Avatar image URL; initials are shown without one.\\n #[props(default)]\\n pub avatar: Option<String>,\\n /// Role pill, e.g. \\\"Admin\\\".\\n #[props(default)]\\n pub role: Option<String>,\\n /// Trailing actions.\\n #[props(default)]\\n pub actions: Option<Element>,\\n /// Replaces the harness's `motion.prefersReduced`.\\n #[props(default)]\\n pub prefers_reduced_motion: bool,\\n /// Extra classes, appended last.\\n #[props(default)]\\n pub class: String,\\n}\\n\\n/// A person, with avatar, name, role and actions.\\n#[component]\\npub fn MziziUserCard(props: MziziUserCardProps) -> Element {\\n if props.loading {\\n return rsx! {\\n div {\\n \\\"data-slot\\\": \\\"user-card\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/user-card\\\",\\n \\\"data-loading\\\": \\\"\\\",\\n role: \\\"article\\\",\\n class: \\\"flex animate-pulse items-center gap-3 rounded-[var(--radius-lg,14px)] bg-card p-3 ring-1 ring-foreground/10\\\",\\n div { class: \\\"size-10 shrink-0 rounded-full bg-muted\\\" }\\n div { class: \\\"flex-1 space-y-1.5\\\",\\n div { class: \\\"h-3.5 w-1/3 rounded bg-muted\\\" }\\n div { class: \\\"h-2.5 w-1/4 rounded bg-muted\\\" }\\n }\\n }\\n };\\n }\\n let root = if props.class.is_empty() {\\n ROOT.to_owned()\\n } else {\\n format!(\\\"{ROOT} {}\\\", props.class)\\n };\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"user-card\\\",\\n style: entrance_style(props.prefers_reduced_motion),\\n role: \\\"article\\\",\\n class: \\\"{root}\\\",\\n div { class: \\\"size-12 shrink-0 overflow-hidden rounded-full\\\",\\n if let Some(avatar) = &props.avatar {\\n img { src: \\\"{avatar}\\\", alt: \\\"{props.name}\\\", class: \\\"size-full object-cover\\\" }\\n } else {\\n div { class: \\\"flex size-full items-center justify-center bg-muted text-base font-medium text-muted-foreground\\\",\\n {initials(&props.name)}\\n }\\n }\\n }\\n div { class: \\\"min-w-0 flex-1\\\",\\n div { class: \\\"flex items-center gap-2\\\",\\n p { class: \\\"truncate font-medium\\\", \\\"{props.name}\\\" }\\n if let Some(role) = &props.role {\\n span { class: \\\"shrink-0 rounded-4xl bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary\\\", \\\"{role}\\\" }\\n }\\n }\\n if let Some(email) = &props.email {\\n p { class: \\\"mt-0.5 truncate text-xs text-muted-foreground\\\", \\\"{email}\\\" }\\n }\\n }\\n if let Some(actions) = &props.actions {\\n div { class: \\\"flex shrink-0 items-center gap-1\\\", {actions} }\\n }\\n }\\n }\\n}\\n\\n#[cfg(test)]\\nmod tests {\\n use super::*;\\n\\n #[test]\\n fn initials_take_two_words() {\\n assert_eq!(initials(\\\"Rudo Chikomo\\\"), \\\"RC\\\");\\n assert_eq!(initials(\\\"Farai\\\"), \\\"F\\\");\\n assert_eq!(initials(\\\"Tariro Rufaro Dube\\\"), \\\"TR\\\");\\n }\\n\\n #[test]\\n fn a_double_space_does_not_eat_an_initial() {\\n assert_eq!(initials(\\\"Rudo Chikomo\\\"), \\\"RC\\\");\\n }\\n}\\n\"}],\"contract\":\"contract\\n slot is \\\"user-card\\\"\\n role is \\\"article\\\"\\n class contains \\\"rounded-2xl\\\"\\nend\"}",
|
|
1241
|
+
"progress": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"progress\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Displays an indicator showing the completion progress of a task.\",\"crate\":{\"name\":\"mzizi-ui\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n2-primitives/progress.rs\",\"type\":\"registry:rust\",\"content\":\"//! PROGRESS — N2 primitive, Dioxus.\\n//!\\n//! The Rust sibling of `progress.tsx`, which wraps Radix's `Progress.Root`/`Indicator`.\\n//!\\n//! One thing here is a CONTRACT decision, not a translation: `progress.tsx` destructures\\n//! `value` out of its own props and never spreads it back onto `ProgressPrimitive.Root`\\n//! — only onto the indicator's inline-style transform. Radix's own `aria-valuenow` and\\n//! `data-state` therefore never see the real value in the TypeScript today, which means\\n//! every progress bar reports as indeterminate to assistive technology regardless of its\\n//! visual fill. Registry issue #223 lists `role=\\\"progressbar\\\"` with a working\\n//! `aria-valuenow`/min/max and a real indeterminate state as what this primitive HAS to\\n//! get right — so this port implements the working contract Radix intends, not the gap\\n//! in the current wrapper (registry issue #222, rule 1: written against the contract,\\n//! with the TypeScript as reference, not input).\\n//!\\n//! `registry #223`: 13 dependents.\\n\\nuse dioxus::prelude::*;\\n\\n/// Progress state, matching Radix's own `data-state` values and their derivation:\\n/// no value at all is indeterminate; a value at the max is complete; anything else is\\n/// in progress.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\\nenum ProgressState {\\n Indeterminate,\\n Loading,\\n Complete,\\n}\\n\\nimpl ProgressState {\\n const fn slug(self) -> &'static str {\\n match self {\\n Self::Indeterminate => \\\"indeterminate\\\",\\n Self::Loading => \\\"loading\\\",\\n Self::Complete => \\\"complete\\\",\\n }\\n }\\n}\\n\\nfn state_for(value: Option<u8>, max: u8) -> ProgressState {\\n match value {\\n None => ProgressState::Indeterminate,\\n Some(v) if v >= max => ProgressState::Complete,\\n Some(_) => ProgressState::Loading,\\n }\\n}\\n\\n/// The classes the outer track carries.\\nconst TRACK: &str = \\\"relative flex h-3 w-full items-center overflow-x-hidden rounded-full bg-muted\\\";\\n\\n/// The classes the fill indicator carries.\\nconst INDICATOR: &str = \\\"size-full flex-1 bg-primary transition-all\\\";\\n\\n/// Compose the full class string for the outer track.\\npub fn progress_variants(extra: &str) -> String {\\n let mut out = String::with_capacity(TRACK.len() + 32);\\n out.push_str(TRACK);\\n if !extra.is_empty() {\\n out.push(' ');\\n out.push_str(extra);\\n }\\n out\\n}\\n\\n/// Props for [`Progress`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct ProgressProps {\\n /// Current value, 0 to `max`. `None` renders the indeterminate state — a Radix\\n /// progress bar with no value at all, not a `0`, which is a real (empty) reading.\\n #[props(default)]\\n pub value: Option<u8>,\\n /// The value that counts as complete. Radix's own default.\\n #[props(default = 100)]\\n pub max: u8,\\n /// Extra classes, appended last so a consumer can override.\\n #[props(default)]\\n pub class: String,\\n /// Any other HTML attribute.\\n #[props(extends = GlobalAttributes)]\\n pub attributes: Vec<Attribute>,\\n}\\n\\n/// A progress bar.\\n///\\n/// ```ignore\\n/// rsx! { Progress { value: 40 } }\\n/// rsx! { Progress {} } // indeterminate — no value yet\\n/// ```\\n#[component]\\npub fn Progress(props: ProgressProps) -> Element {\\n let state = state_for(props.value, props.max);\\n let percent = props.value.unwrap_or(0).min(props.max);\\n let transform = format!(\\n \\\"transform: translateX(-{}%);\\\",\\n 100u16.saturating_sub(u16::from(percent))\\n );\\n\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"progress\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/progress\\\",\\n \\\"data-state\\\": state.slug(),\\n \\\"data-value\\\": props.value.map(|v| v.to_string()),\\n \\\"data-max\\\": \\\"{props.max}\\\",\\n role: \\\"progressbar\\\",\\n \\\"aria-valuemin\\\": \\\"0\\\",\\n \\\"aria-valuemax\\\": \\\"{props.max}\\\",\\n \\\"aria-valuenow\\\": props.value.map(|v| v.to_string()),\\n class: progress_variants(&props.class),\\n ..props.attributes,\\n div {\\n \\\"data-slot\\\": \\\"progress-indicator\\\",\\n \\\"data-state\\\": state.slug(),\\n class: INDICATOR,\\n style: \\\"{transform}\\\",\\n }\\n }\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1242
|
+
"rtl-conformity-check": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"rtl-conformity-check\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"N8 assurance probe that validates RTL (right-to-left) layout correctness across deployed components. Checks: logical properties (start/end vs left/right), bidi text rendering, icon mirroring rules, text alignment consistency. Audit flagged as missing accessibility infrastructure. ALPHA — awaiting edge function implementation.\",\"crate\":{\"name\":\"mzizi-assurance\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n8-assurance/rtl-conformity-check.rs\",\"type\":\"registry:rust\",\"content\":\"//! Mzizi N8 assurance — is this layout actually right-to-left correct?\\n//!\\n//! The Rust implementation of `rtl-conformity-check`. Five rules over what the\\n//! host observed; the host walks the DOM and reads the computed styles, because\\n//! that is the only part that needs a browser.\\n//!\\n//! # Six defects in the TypeScript, fixed here\\n//!\\n//! **1. The most serious rule did not affect the score.** `direction-attribute` —\\n//! \\\"`<html>` has no `dir`, so RTL locales fall back to LTR\\\", the one finding that\\n//! is about the whole page rather than one element — is pushed AFTER the element\\n//! loop, and `passes` is only ever incremented inside it. So a page whose sole\\n//! defect is that RTL does not work at all scores 100. [`audit`] counts it.\\n//!\\n//! **2. The icon rule only ran when the page was already RTL.** `docDir === \\\"rtl\\\"`\\n//! gates it, so the check that finds icons needing mirroring never fires on the\\n//! LTR build — which is the build almost everyone audits. And the finding is a\\n//! property of the icon, not of the current render: a chevron with no mirroring\\n//! hint will be wrong in RTL whether or not you are looking at RTL right now. The\\n//! direction is still reported; it no longer decides whether to look.\\n//!\\n//! **3. Name matching was substring matching, in both directions.**\\n//! `\\\"research-icon\\\".includes(\\\"search-icon\\\")` is true, so `research-icon` inherited\\n//! the non-mirroring exemption meant for the magnifying glass. And\\n//! `isDirectionalIcon` matches `\\\"back\\\"`, so `background-icon` is reported as a\\n//! directional icon needing mirroring. Both are matched on hyphen-separated\\n//! segments here.\\n//!\\n//! **4. The bidi detector missed most real Arabic and Hebrew.** The range list\\n//! covers the base blocks and stops, so Arabic Supplement (U+0750-U+077F), Arabic\\n//! Extended-A (U+08A0-U+08FF) and — the big one — the Presentation Forms\\n//! (U+FB1D-U+FDFF, U+FE70-U+FEFF) all read as non-bidi. Presentation forms are\\n//! where a great deal of real-world Arabic arrives, particularly out of PDFs and\\n//! older systems, so the rule passed exactly the content most likely to need it.\\n//!\\n//! **5. The selector broke on an id that is not an identifier.** `#${el.id}` raw,\\n//! same as `mzizi-a11y-audit`, and the selector's only job is to find the element\\n//! again.\\n//!\\n//! **6. The node guess can only answer 2, 3 or 6.** Same heuristic as\\n//! `mzizi-a11y-audit`, same consequence: an N7 shell component is labelled N6 and\\n//! routed to the wrong owner. The registry wins; the heuristic is the fallback.\\n\\nuse std::collections::{BTreeMap, BTreeSet};\\n\\n/// The direction a document declares.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\\npub enum Direction {\\n /// Left to right.\\n #[default]\\n Ltr,\\n /// Right to left.\\n Rtl,\\n /// Per-element, decided by the browser.\\n Auto,\\n}\\n\\nimpl Direction {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Ltr => \\\"ltr\\\",\\n Self::Rtl => \\\"rtl\\\",\\n Self::Auto => \\\"auto\\\",\\n }\\n }\\n\\n /// Parse a `dir` attribute value.\\n ///\\n /// [`None`] means the attribute was absent, which is what\\n /// [`RtlRule::DirectionAttribute`] is about — distinct from a `dir` that says\\n /// `ltr`, which is a deliberate answer.\\n #[must_use]\\n pub fn parse(value: &str) -> Option<Self> {\\n match value.trim().to_ascii_lowercase().as_str() {\\n \\\"ltr\\\" => Some(Self::Ltr),\\n \\\"rtl\\\" => Some(Self::Rtl),\\n \\\"auto\\\" => Some(Self::Auto),\\n _ => None,\\n }\\n }\\n}\\n\\n/// How much an RTL violation matters.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum RtlLevel {\\n /// Advisory.\\n Minor,\\n /// Wrong for some content.\\n Moderate,\\n /// Wrong for a whole locale.\\n Serious,\\n /// Unusable.\\n Critical,\\n}\\n\\nimpl RtlLevel {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::Critical => \\\"critical\\\",\\n Self::Serious => \\\"serious\\\",\\n Self::Moderate => \\\"moderate\\\",\\n Self::Minor => \\\"minor\\\",\\n }\\n }\\n}\\n\\n/// Which rule produced a finding.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]\\npub enum RtlRule {\\n /// A physical CSS property where a logical one belongs.\\n LogicalProperties,\\n /// Bidi text with no `lang` or `dir`.\\n BidiText,\\n /// A directional icon with no mirroring hint.\\n IconMirroring,\\n /// `text-align: left` or `right`.\\n TextAlignment,\\n /// `<html>` carries no `dir`.\\n DirectionAttribute,\\n}\\n\\nimpl RtlRule {\\n /// The wire spelling, matching the `.ts` string union.\\n #[must_use]\\n pub const fn as_str(self) -> &'static str {\\n match self {\\n Self::LogicalProperties => \\\"logical-properties\\\",\\n Self::BidiText => \\\"bidi-text\\\",\\n Self::IconMirroring => \\\"icon-mirroring\\\",\\n Self::TextAlignment => \\\"text-alignment\\\",\\n Self::DirectionAttribute => \\\"direction-attribute\\\",\\n }\\n }\\n\\n /// Every rule.\\n ///\\n /// One list. The `.ts` writes the rule names twice — once in the type union\\n /// and once in the runtime default array — so adding a rule to one and not\\n /// the other is a silently disabled rule.\\n #[must_use]\\n pub fn all() -> BTreeSet<Self> {\\n [\\n Self::LogicalProperties,\\n Self::BidiText,\\n Self::IconMirroring,\\n Self::TextAlignment,\\n Self::DirectionAttribute,\\n ]\\n .into_iter()\\n .collect()\\n }\\n}\\n\\n/// The physical CSS properties that have logical counterparts.\\npub const PHYSICAL_PROPERTIES: [&str; 12] = [\\n \\\"margin-left\\\",\\n \\\"margin-right\\\",\\n \\\"padding-left\\\",\\n \\\"padding-right\\\",\\n \\\"left\\\",\\n \\\"right\\\",\\n \\\"border-left\\\",\\n \\\"border-right\\\",\\n \\\"border-top-left-radius\\\",\\n \\\"border-top-right-radius\\\",\\n \\\"border-bottom-left-radius\\\",\\n \\\"border-bottom-right-radius\\\",\\n];\\n\\n/// Icon names that must NOT mirror in RTL.\\npub const NON_MIRRORING_ICONS: [&str; 15] = [\\n \\\"search-icon\\\",\\n \\\"play-icon\\\",\\n \\\"pause-icon\\\",\\n \\\"volume-icon\\\",\\n \\\"mute-icon\\\",\\n \\\"clock-icon\\\",\\n \\\"calendar-icon\\\",\\n \\\"check-icon\\\",\\n \\\"x-icon\\\",\\n \\\"heart-icon\\\",\\n \\\"star-icon\\\",\\n \\\"info-icon\\\",\\n \\\"warning-icon\\\",\\n \\\"error-icon\\\",\\n \\\"success-icon\\\",\\n];\\n\\n/// Name fragments that mark an icon as directional.\\npub const DIRECTIONAL_FRAGMENTS: [&str; 11] = [\\n \\\"arrow\\\", \\\"chevron\\\", \\\"caret\\\", \\\"back\\\", \\\"forward\\\", \\\"next\\\", \\\"prev\\\", \\\"undo\\\", \\\"redo\\\", \\\"reply\\\", \\\"send\\\",\\n];\\n\\n/// Split a name into hyphen- and underscore-separated segments, lowercased.\\n///\\n/// Segment matching, not substring matching. `\\\"research-icon\\\".includes(\\\"search\\\")`\\n/// is true and `\\\"background-icon\\\".includes(\\\"back\\\")` is true, which is how the\\n/// `.ts` exempts a research icon from mirroring and demands that a background\\n/// image be mirrored.\\n#[must_use]\\npub fn segments(name: &str) -> Vec<String> {\\n name.split(['-', '_', ' ', '.', '/'])\\n .filter(|s| !s.is_empty())\\n .map(str::to_ascii_lowercase)\\n .collect()\\n}\\n\\n/// Whether this icon is on the never-mirror list.\\n///\\n/// Compared segment-wise: `search-icon` exempts `search-icon` and `icon-search`,\\n/// and does not exempt `research-icon`.\\n#[must_use]\\npub fn is_non_mirroring(name: &str, non_mirroring: &BTreeSet<String>) -> bool {\\n let parts: BTreeSet<String> = segments(name).into_iter().collect();\\n non_mirroring\\n .iter()\\n .any(|entry| segments(entry).iter().all(|seg| parts.contains(seg)))\\n}\\n\\n/// Whether this icon points somewhere, and so may need mirroring.\\n#[must_use]\\npub fn is_directional(name: &str) -> bool {\\n let parts = segments(name);\\n DIRECTIONAL_FRAGMENTS\\n .iter()\\n .any(|fragment| parts.iter().any(|seg| seg == fragment))\\n}\\n\\n/// Whether a string contains characters from a right-to-left script.\\n///\\n/// Covers what the `.ts` covers PLUS Arabic Supplement, Arabic Extended-A and the\\n/// Presentation Forms — the blocks a great deal of real-world Arabic and Hebrew\\n/// actually arrives in, and which the `.ts` reads as non-bidi.\\n#[must_use]\\npub fn has_bidi_chars(text: &str) -> bool {\\n text.chars().any(|c| {\\n matches!(c as u32,\\n 0x0590..=0x05FF // Hebrew\\n | 0x0600..=0x06FF // Arabic\\n | 0x0700..=0x074F // Syriac\\n | 0x0750..=0x077F // Arabic Supplement — missing from the .ts\\n | 0x0780..=0x07BF // Thaana\\n | 0x07C0..=0x07FF // NKo\\n | 0x0800..=0x083F // Samaritan\\n | 0x0840..=0x085F // Mandaic\\n | 0x08A0..=0x08FF // Arabic Extended-A — missing from the .ts\\n | 0xFB1D..=0xFB4F // Hebrew Presentation Forms — missing\\n | 0xFB50..=0xFDFF // Arabic Presentation Forms-A — missing\\n | 0xFE70..=0xFEFF // Arabic Presentation Forms-B — missing\\n )\\n })\\n}\\n\\n/// One element as the host observed it.\\n#[derive(Debug, Clone, PartialEq, Eq, Default)]\\npub struct ObservedNode {\\n /// Uppercase tag name.\\n pub tag_name: String,\\n /// The `id`.\\n pub id: Option<String>,\\n /// The first class.\\n pub first_class: Option<String>,\\n /// The `data-slot` value.\\n pub data_slot: Option<String>,\\n /// The `data-portal` backlink.\\n pub data_portal: Option<String>,\\n /// The `aria-label`.\\n pub aria_label: Option<String>,\\n /// The `lang` attribute.\\n pub lang: Option<String>,\\n /// The `dir` attribute.\\n pub dir: Option<String>,\\n /// Rendered text.\\n pub text: Option<String>,\\n /// Inline style declarations the host read, property to value.\\n pub inline_styles: BTreeMap<String, String>,\\n /// The `data-rtl-mirror` marker.\\n pub rtl_mirror_hint: Option<String>,\\n}\\n\\nimpl ObservedNode {\\n /// The name this element's icon goes by.\\n #[must_use]\\n pub fn icon_name(&self) -> String {\\n self.data_slot\\n .clone()\\n .or_else(|| self.aria_label.clone())\\n .unwrap_or_default()\\n }\\n\\n /// Whether this element is an icon.\\n #[must_use]\\n pub fn is_icon(&self) -> bool {\\n self.data_slot.as_deref() == Some(\\\"icon\\\") || self.tag_name.eq_ignore_ascii_case(\\\"svg\\\")\\n }\\n\\n /// Whether bidi text here would need a `lang` or `dir`.\\n #[must_use]\\n pub fn carries_prose(&self) -> bool {\\n [\\\"P\\\", \\\"SPAN\\\", \\\"DIV\\\"]\\n .iter()\\n .any(|tag| self.tag_name.eq_ignore_ascii_case(tag))\\n }\\n}\\n\\n/// One finding.\\n#[derive(Debug, Clone, PartialEq, Eq)]\\npub struct RtlViolation {\\n /// Which rule.\\n pub rule: RtlRule,\\n /// How much it matters.\\n pub level: RtlLevel,\\n /// What is wrong.\\n pub message: String,\\n /// The element's tag.\\n pub element: String,\\n /// A selector that finds it again.\\n pub selector: String,\\n /// Which helix node, when known.\\n pub node: Option<u32>,\\n /// The `data-slot` value.\\n pub component_name: Option<String>,\\n /// Where the documentation is.\\n pub portal_url: Option<String>,\\n /// How to fix it.\\n pub fix: String,\\n}\\n\\n/// What a page scored.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct RtlAuditResult {\\n /// The URL audited.\\n pub url: String,\\n /// What `<html dir>` said, when it said anything.\\n pub direction: Option<Direction>,\\n /// How many elements were observed.\\n pub total_elements: usize,\\n /// Every finding.\\n pub violations: Vec<RtlViolation>,\\n /// How many checks passed.\\n pub passes: usize,\\n /// Percentage passing, 0-100.\\n pub score: u32,\\n}\\n\\n/// What to check.\\n#[derive(Debug, Clone, PartialEq)]\\npub struct RtlConfig {\\n /// Which rules to run. [`None`] means all; an empty set means none.\\n pub rules: Option<BTreeSet<RtlRule>>,\\n /// Icons that must never mirror.\\n pub non_mirroring_icons: BTreeSet<String>,\\n}\\n\\nimpl Default for RtlConfig {\\n fn default() -> Self {\\n Self {\\n rules: None,\\n non_mirroring_icons: NON_MIRRORING_ICONS\\n .iter()\\n .map(|s| (*s).to_owned())\\n .collect(),\\n }\\n }\\n}\\n\\nimpl RtlConfig {\\n /// Whether a rule is enabled.\\n #[must_use]\\n pub fn runs(&self, rule: RtlRule) -> bool {\\n self.rules.as_ref().is_none_or(|set| set.contains(&rule))\\n }\\n}\\n\\n/// Whether a string is usable bare after `#` in a selector.\\nfn is_plain_ident(value: &str) -> bool {\\n let mut chars = value.chars();\\n match chars.next() {\\n Some(first) if first.is_ascii_alphabetic() || first == '_' => {}\\n _ => return false,\\n }\\n chars.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')\\n}\\n\\nfn escape_attr(value: &str) -> String {\\n value.replace('\\\\\\\\', \\\"\\\\\\\\\\\\\\\\\\\").replace('\\\"', \\\"\\\\\\\\\\\\\\\"\\\")\\n}\\n\\n/// A selector that finds this element again.\\n#[must_use]\\npub fn css_selector(node: &ObservedNode) -> String {\\n if let Some(id) = node.id.as_deref().filter(|s| !s.is_empty()) {\\n return if is_plain_ident(id) {\\n format!(\\\"#{id}\\\")\\n } else {\\n format!(\\\"[id=\\\\\\\"{}\\\\\\\"]\\\", escape_attr(id))\\n };\\n }\\n if let Some(slot) = node.data_slot.as_deref().filter(|s| !s.is_empty()) {\\n return format!(\\\"[data-slot=\\\\\\\"{}\\\\\\\"]\\\", escape_attr(slot));\\n }\\n let tag = node.tag_name.to_ascii_lowercase();\\n match node.first_class.as_deref().filter(|s| !s.is_empty()) {\\n Some(class) if is_plain_ident(class) => format!(\\\"{tag}.{class}\\\"),\\n Some(class) => format!(\\\"{tag}[class~=\\\\\\\"{}\\\\\\\"]\\\", escape_attr(class)),\\n None => tag,\\n }\\n}\\n\\n/// The `.ts`'s slot heuristic. Can only answer 2, 3 or 6 — prefer [`resolve_node`].\\n#[must_use]\\npub fn guess_node_from_slot(slot: &str) -> Option<u32> {\\n if slot.is_empty() {\\n return None;\\n }\\n if (slot.starts_with(\\\"mzizi-\\\") || slot.starts_with(\\\"nyuchi-\\\")) && !slot.contains(\\\"page\\\") {\\n return Some(3);\\n }\\n if slot.contains(\\\"page\\\") || slot.contains(\\\"layout\\\") {\\n return Some(6);\\n }\\n Some(2)\\n}\\n\\n/// Which node owns the component in this slot. The registry wins.\\n#[must_use]\\npub fn resolve_node(slot: Option<&str>, registry: &BTreeMap<String, u32>) -> Option<u32> {\\n let slot = slot?;\\n registry\\n .get(slot)\\n .copied()\\n .or_else(|| guess_node_from_slot(slot))\\n}\\n\\nfn violation(\\n node: &ObservedNode,\\n helix_node: Option<u32>,\\n rule: RtlRule,\\n level: RtlLevel,\\n message: String,\\n fix: String,\\n) -> RtlViolation {\\n RtlViolation {\\n rule,\\n level,\\n message,\\n element: node.tag_name.clone(),\\n selector: css_selector(node),\\n node: helix_node,\\n component_name: node.data_slot.clone().filter(|s| !s.is_empty()),\\n portal_url: node.data_portal.clone().filter(|s| !s.is_empty()),\\n fix,\\n }\\n}\\n\\n/// Check one element against every enabled rule.\\n///\\n/// Returns every physical property found, not the first — matching the `.ts`,\\n/// which is right: twelve wrong properties on one element are twelve edits.\\n#[must_use]\\npub fn check_node(\\n node: &ObservedNode,\\n config: &RtlConfig,\\n registry: &BTreeMap<String, u32>,\\n) -> Vec<RtlViolation> {\\n let mut found = Vec::new();\\n let helix_node = resolve_node(node.data_slot.as_deref(), registry);\\n\\n if config.runs(RtlRule::LogicalProperties) {\\n for property in PHYSICAL_PROPERTIES {\\n if node\\n .inline_styles\\n .get(property)\\n .is_some_and(|v| !v.trim().is_empty())\\n {\\n found.push(violation(\\n node,\\n helix_node,\\n RtlRule::LogicalProperties,\\n RtlLevel::Serious,\\n format!(\\n \\\"Inline style uses physical \\\\\\\"{property}\\\\\\\" — use logical equivalent \\\\\\n (margin-inline-start, padding-inline-end, inset-inline-start, etc.)\\\"\\n ),\\n format!(\\\"Replace {property} with its inline-/block- logical counterpart\\\"),\\n ));\\n }\\n }\\n }\\n\\n if config.runs(RtlRule::BidiText)\\n && node.carries_prose()\\n && node.lang.is_none()\\n && node.dir.is_none()\\n && node.text.as_deref().is_some_and(has_bidi_chars)\\n {\\n found.push(violation(\\n node,\\n helix_node,\\n RtlRule::BidiText,\\n RtlLevel::Moderate,\\n \\\"Element contains bidi text but is missing lang or dir attribute\\\".to_owned(),\\n \\\"Add lang=\\\\\\\"ar\\\\\\\" (or appropriate) or dir=\\\\\\\"auto\\\\\\\" so the browser can render bidi \\\\\\n correctly\\\"\\n .to_owned(),\\n ));\\n }\\n\\n // Evaluated whatever the document direction is. The .ts gates this on\\n // `docDir === \\\"rtl\\\"`, so it never fires on the LTR build almost everyone\\n // audits — and whether a chevron has a mirroring hint is a property of the\\n // icon, not of the render you happen to be looking at.\\n if config.runs(RtlRule::IconMirroring) && node.is_icon() {\\n let name = node.icon_name();\\n let has_hint = node.rtl_mirror_hint.is_some()\\n || node\\n .inline_styles\\n .get(\\\"transform\\\")\\n .is_some_and(|v| !v.trim().is_empty());\\n if !has_hint\\n && is_directional(&name)\\n && !is_non_mirroring(&name, &config.non_mirroring_icons)\\n {\\n found.push(violation(\\n node,\\n helix_node,\\n RtlRule::IconMirroring,\\n RtlLevel::Minor,\\n format!(\\\"Directional icon \\\\\\\"{name}\\\\\\\" may need mirroring in RTL contexts\\\"),\\n \\\"Add data-rtl-mirror=\\\\\\\"true\\\\\\\" or apply CSS [dir=rtl] & { transform: scaleX(-1) }\\\"\\n .to_owned(),\\n ));\\n }\\n }\\n\\n if config.runs(RtlRule::TextAlignment)\\n && let Some(align) = node.inline_styles.get(\\\"text-align\\\")\\n && matches!(align.trim(), \\\"left\\\" | \\\"right\\\")\\n {\\n let align = align.trim();\\n let logical = if align == \\\"left\\\" { \\\"start\\\" } else { \\\"end\\\" };\\n found.push(violation(\\n node,\\n helix_node,\\n RtlRule::TextAlignment,\\n RtlLevel::Moderate,\\n format!(\\n \\\"text-align: {align} is physical — use \\\\\\\"start\\\\\\\" or \\\\\\\"end\\\\\\\" for RTL-aware alignment\\\"\\n ),\\n format!(\\\"Replace text-align: {align} with text-align: {logical}\\\"),\\n ));\\n }\\n\\n found\\n}\\n\\n/// Audit a page.\\n///\\n/// `document_dir` is what `<html dir>` said, or [`None`] when it said nothing —\\n/// which is itself the [`RtlRule::DirectionAttribute`] finding.\\n///\\n/// The document-level rule COUNTS toward the score. In the `.ts` it is pushed\\n/// after the element loop and `passes` only ever moves inside it, so a page whose\\n/// sole defect is that RTL does not work at all scored 100.\\n#[must_use]\\npub fn audit(\\n url: impl Into<String>,\\n document_dir: Option<Direction>,\\n nodes: &[ObservedNode],\\n config: &RtlConfig,\\n registry: &BTreeMap<String, u32>,\\n) -> RtlAuditResult {\\n let mut violations = Vec::new();\\n let mut passes = 0usize;\\n // The document-level rule is one check in its own right, so the denominator\\n // is elements plus one whenever it runs.\\n let mut checks = nodes.len();\\n\\n for node in nodes {\\n let found = check_node(node, config, registry);\\n if found.is_empty() {\\n passes += 1;\\n }\\n violations.extend(found);\\n }\\n\\n if config.runs(RtlRule::DirectionAttribute) {\\n checks += 1;\\n if document_dir.is_none() {\\n violations.push(RtlViolation {\\n rule: RtlRule::DirectionAttribute,\\n level: RtlLevel::Serious,\\n message: \\\"<html> element has no dir attribute — RTL locales will fall back to LTR\\\"\\n .to_owned(),\\n element: \\\"HTML\\\".to_owned(),\\n selector: \\\"html\\\".to_owned(),\\n node: None,\\n component_name: None,\\n portal_url: None,\\n fix: \\\"Add dir=\\\\\\\"ltr\\\\\\\" or dir=\\\\\\\"rtl\\\\\\\" (or a direction provider) on <html>\\\"\\n .to_owned(),\\n });\\n } else {\\n passes += 1;\\n }\\n }\\n\\n RtlAuditResult {\\n url: url.into(),\\n direction: document_dir,\\n total_elements: nodes.len(),\\n violations,\\n passes,\\n score: if checks == 0 {\\n 100\\n } else {\\n ((passes as f64 / checks as f64) * 100.0).round() as u32\\n },\\n }\\n}\\n\\n/// The worst level present, for a single go/no-go answer.\\n#[must_use]\\npub fn worst_level(violations: &[RtlViolation]) -> Option<RtlLevel> {\\n violations.iter().map(|v| v.level).max()\\n}\\n\"}],\"contract\":null}",
|
|
1243
|
+
"separator": "{\"$schema\":\"https://ui.shadcn.com/schema/registry-item.json\",\"name\":\"separator\",\"type\":\"registry:ui\",\"target\":\"dioxus\",\"description\":\"Visually or semantically separates content.\",\"crate\":{\"name\":\"mzizi-ui\",\"registry\":\"crates.io\",\"git\":\"https://github.com/mzizi-dev/mzizi-registry\"},\"files\":[{\"path\":\"components/registry/n2-primitives/separator.rs\",\"type\":\"registry:rust\",\"content\":\"//! SEPARATOR — N2 primitive, Dioxus.\\n//!\\n//! The Rust sibling of `separator.tsx`, which wraps Radix's `Separator.Root`. Same\\n//! contract: a `decorative` separator gets `role=\\\"none\\\"` and no `aria-orientation` (a\\n//! screen reader should skip it entirely); a semantic one gets `role=\\\"separator\\\"` and,\\n//! only when vertical, `aria-orientation=\\\"vertical\\\"` (horizontal is the ARIA default for\\n//! `role=\\\"separator\\\"`, so it is omitted rather than stated redundantly — this matches\\n//! Radix's own implementation, not a simplification of it).\\n//!\\n//! Written against the contract, NOT machine-translated from the `.tsx` (registry\\n//! issue #222, rule 1).\\n//!\\n//! `registry #223`: 22 dependents, second-heaviest of primitives wave 1.\\n\\nuse dioxus::prelude::*;\\n\\n/// Which axis the separator runs along.\\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\\npub enum SeparatorOrientation {\\n /// A horizontal rule — full width, one pixel tall.\\n #[default]\\n Horizontal,\\n /// A vertical rule — full height, one pixel wide.\\n Vertical,\\n}\\n\\nimpl SeparatorOrientation {\\n /// The Tailwind classes for this orientation. Matches the `data-horizontal:` /\\n /// `data-vertical:` arbitrary-attribute variants in `separator.tsx` — the underlying\\n /// `data-orientation` value is what both targets key off, so this stays in step with\\n /// `slug()`.\\n pub const fn classes(self) -> &'static str {\\n match self {\\n Self::Horizontal => \\\"h-px w-full\\\",\\n Self::Vertical => \\\"w-px self-stretch\\\",\\n }\\n }\\n\\n /// The `data-orientation` value, matching Radix's own attribute name and values.\\n pub const fn slug(self) -> &'static str {\\n match self {\\n Self::Horizontal => \\\"horizontal\\\",\\n Self::Vertical => \\\"vertical\\\",\\n }\\n }\\n}\\n\\n/// The base classes every separator carries, whatever its orientation.\\nconst BASE: &str = \\\"shrink-0 bg-border\\\";\\n\\n/// Compose the full class string for a separator.\\npub fn separator_variants(orientation: SeparatorOrientation, extra: &str) -> String {\\n let mut out = String::with_capacity(BASE.len() + 64);\\n out.push_str(BASE);\\n out.push(' ');\\n out.push_str(orientation.classes());\\n if !extra.is_empty() {\\n out.push(' ');\\n out.push_str(extra);\\n }\\n out\\n}\\n\\n/// Props for [`Separator`].\\n#[derive(Props, Clone, PartialEq)]\\npub struct SeparatorProps {\\n /// Which axis the rule runs along.\\n #[props(default)]\\n pub orientation: SeparatorOrientation,\\n /// Purely visual (the default) means assistive technology should skip it entirely —\\n /// `role=\\\"none\\\"`, no orientation announced. `false` means it is structural content\\n /// and gets `role=\\\"separator\\\"`.\\n #[props(default = true)]\\n pub decorative: bool,\\n /// Extra classes, appended last so a consumer can override.\\n #[props(default)]\\n pub class: String,\\n /// Any other HTML attribute.\\n #[props(extends = GlobalAttributes)]\\n pub attributes: Vec<Attribute>,\\n}\\n\\n/// A one-pixel visual (or semantic) rule between content.\\n///\\n/// ```ignore\\n/// rsx! { Separator {} }\\n/// rsx! { Separator { orientation: SeparatorOrientation::Vertical, decorative: false } }\\n/// ```\\n#[component]\\npub fn Separator(props: SeparatorProps) -> Element {\\n let role = if props.decorative {\\n \\\"none\\\"\\n } else {\\n \\\"separator\\\"\\n };\\n // Radix omits `aria-orientation` entirely for horizontal — that is the ARIA default\\n // for role=\\\"separator\\\" — and never sets it at all on a decorative one. `None` renders\\n // no attribute at all in Dioxus, which is the behaviour this reproduces.\\n let aria_orientation = (!props.decorative\\n && props.orientation == SeparatorOrientation::Vertical)\\n .then_some(\\\"vertical\\\");\\n rsx! {\\n div {\\n \\\"data-slot\\\": \\\"separator\\\",\\n \\\"data-portal\\\": \\\"https://mzizi.dev/components/separator\\\",\\n \\\"data-orientation\\\": props.orientation.slug(),\\n role,\\n \\\"aria-orientation\\\": aria_orientation,\\n class: separator_variants(props.orientation, &props.class),\\n ..props.attributes,\\n }\\n }\\n}\\n\"}],\"contract\":null}",
|
|
1244
|
+
};
|
|
1245
|
+
/** Component name → the crate that compiles its Rust, for every entry in RUST. Cheap to read whole. */
|
|
1246
|
+
export const RUST_CRATES = { "avatar": "mzizi-ui", "badge": "mzizi-ui", "button": "mzizi-ui", "card": "mzizi-ui", "chart": "mzizi-ui", "input": "mzizi-ui", "label": "mzizi-ui", "mzizi-a11y-audit": "mzizi-assurance", "mzizi-ai-context": "mzizi-docs", "mzizi-alert-banner": "mzizi-brand", "mzizi-alert-engine": "mzizi-assurance", "mzizi-api-probe": "mzizi-assurance", "mzizi-avatar-stack": "mzizi-brand", "mzizi-bottom-nav": "mzizi-shell", "mzizi-changelog-renderer": "mzizi-docs", "mzizi-chaos": "mzizi-assurance", "mzizi-command-palette": "mzizi-shell", "mzizi-conformity-check": "mzizi-assurance", "mzizi-connectivity-bar": "mzizi-shell", "mzizi-cover-header": "mzizi-brand", "mzizi-deep-link-handler": "mzizi-shell", "mzizi-docs-api": "mzizi-docs", "mzizi-docs-engine": "mzizi-docs", "mzizi-empty-state": "mzizi-brand", "mzizi-error-tracker": "mzizi-assurance", "mzizi-escalation-card": "mzizi-brand", "mzizi-footer": "mzizi-shell", "mzizi-fundi": "mzizi-fundi", "mzizi-fundi-learning": "mzizi-fundi", "mzizi-fundi-reporter": "mzizi-fundi", "mzizi-gauge-card": "mzizi-brand", "mzizi-hero-stat": "mzizi-brand", "mzizi-incident-manager": "mzizi-assurance", "mzizi-meta-tile": "mzizi-brand", "mzizi-mini-app-runtime": "mzizi-shell", "mzizi-notification-center": "mzizi-shell", "mzizi-otel": "mzizi-assurance", "mzizi-perf-probe": "mzizi-assurance", "mzizi-persistent-player": "mzizi-shell", "mzizi-platform-health": "mzizi-assurance", "mzizi-route-guard": "mzizi-shell", "mzizi-rum": "mzizi-assurance", "mzizi-seo": "mzizi-discovery", "mzizi-stats-row": "mzizi-brand", "mzizi-success-screen": "mzizi-brand", "mzizi-suitability-card": "mzizi-brand", "mzizi-synthetic-probe": "mzizi-assurance", "mzizi-theme-provider": "mzizi-shell", "mzizi-toast-provider": "mzizi-shell", "mzizi-tokens-rust": "mzizi-tokens", "mzizi-update-prompt": "mzizi-shell", "mzizi-user-card": "mzizi-brand", "progress": "mzizi-ui", "rtl-conformity-check": "mzizi-assurance", "separator": "mzizi-ui" };
|
|
1247
|
+
/** `/rs/{name}` for a component with no Rust, with `{name}` in place of the name. */
|
|
1248
|
+
export const RUST_MISSING = "{\"error\":\"\\\"{name}\\\" has no Rust implementation\",\"message\":\"This component ships for React only. The contract, tokens and variants are on https://api.mzizi.dev/v1/ui/{name} — the Dioxus source is yours to write against them.\"}";
|
|
1183
1249
|
/** `/architecture/nodes/{n}` per node or rung. */
|
|
1184
1250
|
export const NODES = {
|
|
1185
1251
|
"1": "{\"data\":{\"node_number\":1,\"sub_label\":\"tokens\",\"title\":\"Design tokens\",\"type\":\"node\",\"strand\":\"swappable\",\"backbone\":\"engineering\",\"role\":\"N1 — the only node allowed to define CSS values\",\"covenant\":\"Design decisions are data, not code.\",\"description\":\"Design decisions expressed as CSS custom properties. The token generation toolchain reads the Supabase styling collections and writes the generated palette plus each target's token file; it is TypeScript today (pnpm tokens:sync, scripts/sync-tokens.ts), and a Rust CLI binary is the target. Every other node reads tokens via var() or its platform equivalent — no node above ever declares its own colour, radius, spacing or motion duration. N1 is target-agnostic: what it emits varies by target, what it guarantees does not.\",\"stakeholder\":\"Design system maintainer\",\"implementation_rules\":[\"Define every value in :root and .dark blocks.\",\"Register tokens in the Tailwind @theme inline block so utility classes resolve.\",\"No hardcoded hex values, rgba(), or inline style={{}} outside N1.\",\"Category-to-mineral mapping lives here, not in components.\"],\"sort_order\":1,\"component_count\":20},\"meta\":{\"model\":\"mzizi-dna-helix\",\"version\":\"v1\"}}",
|