@nurix/etna 0.19.11 → 0.19.12
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/data/harness-index.json +1 -1
- package/data/harnesses/code/harness.json +1 -1
- package/data/harnesses/code/rules/frontend.md +5 -5
- package/data/harnesses/code/skills/design/SKILL.md +70 -50
- package/data/harnesses/code/skills/design/components/charts.md +76 -0
- package/data/harnesses/code/skills/design/components/collections.md +132 -0
- package/data/harnesses/code/skills/design/components/feedback.md +47 -0
- package/data/harnesses/code/skills/design/components/hooks.md +35 -0
- package/data/harnesses/code/skills/design/components/index.md +143 -0
- package/data/harnesses/code/skills/design/components/inputs.md +327 -0
- package/data/harnesses/code/skills/design/components/layout.md +52 -0
- package/data/harnesses/code/skills/design/components/media.md +66 -0
- package/data/harnesses/code/skills/design/components/motion.md +82 -0
- package/data/harnesses/code/skills/design/components/navigation.md +29 -0
- package/data/harnesses/code/skills/design/components/overlays.md +72 -0
- package/data/harnesses/code/skills/design/components/setup.md +40 -0
- package/data/harnesses/code/skills/design/components/typography.md +46 -0
- package/data/harnesses/code/skills/design/components/utils.md +37 -0
- package/data/harnesses/code/skills/design/data/blueprints/browse.md +38 -0
- package/data/harnesses/code/skills/design/data/blueprints/creation-flow.md +38 -0
- package/data/harnesses/code/skills/design/data/blueprints/dashboard.md +38 -0
- package/data/harnesses/code/skills/design/data/blueprints/grid.md +38 -0
- package/data/harnesses/code/skills/design/data/blueprints/queue.md +38 -0
- package/data/harnesses/code/skills/design/data/blueprints/record-detail.md +38 -0
- package/data/harnesses/code/skills/design/data/blueprints/settings.md +38 -0
- package/data/harnesses/code/skills/design/data/chart-styling/Readme.md +33 -0
- package/data/harnesses/code/skills/design/data/chart-styling/carbon-accessible-alert-semantic.md +19 -0
- package/data/harnesses/code/skills/design/data/chart-styling/carbon-monochrome-sequential-trend.md +19 -0
- package/data/harnesses/code/skills/design/data/chart-styling/datadog-classic-six-aligned.md +19 -0
- package/data/harnesses/code/skills/design/data/chart-styling/datawrapper-tiered-gray-hierarchy.md +19 -0
- package/data/harnesses/code/skills/design/data/chart-styling/econ-red-rule-horizontal.md +19 -0
- package/data/harnesses/code/skills/design/data/chart-styling/financial-diverging-threshold-overlay.md +19 -0
- package/data/harnesses/code/skills/design/data/chart-styling/ft-vocabulary-ranked-muted.md +19 -0
- package/data/harnesses/code/skills/design/data/chart-styling/grafana-dark-threshold-bands.md +19 -0
- package/data/harnesses/code/skills/design/data/chart-styling/material-rounded-explicit-label.md +19 -0
- package/data/harnesses/code/skills/design/data/chart-styling/nyt-ghost-grid-direct-label.md +19 -0
- package/data/harnesses/code/skills/design/data/chart-styling/plot-currentcolor-inherit.md +19 -0
- package/data/harnesses/code/skills/design/data/chart-styling/saas-gradient-glow-area.md +19 -0
- package/data/harnesses/code/skills/design/data/chart-styling/shadcn-composable-css-vars.md +19 -0
- package/data/harnesses/code/skills/design/data/charts/census.csv +58 -0
- package/data/harnesses/code/skills/design/data/checklists/browse.md +21 -0
- package/data/harnesses/code/skills/design/data/checklists/creation-flow.md +20 -0
- package/data/harnesses/code/skills/design/data/checklists/dashboard.md +21 -0
- package/data/harnesses/code/skills/design/data/checklists/grid.md +21 -0
- package/data/harnesses/code/skills/design/data/checklists/queue.md +21 -0
- package/data/harnesses/code/skills/design/data/checklists/record-detail.md +21 -0
- package/data/harnesses/code/skills/design/data/checklists/settings.md +21 -0
- package/data/harnesses/code/skills/design/data/chrome/board-column.md +68 -0
- package/data/harnesses/code/skills/design/data/chrome/data-table.md +63 -0
- package/data/harnesses/code/skills/design/data/chrome/side-panel.md +60 -0
- package/data/harnesses/code/skills/design/data/chrome/sidebar.md +78 -0
- package/data/harnesses/code/skills/design/data/chrome/stat-cards.md +62 -0
- package/data/harnesses/code/skills/design/data/chrome/topbar.md +65 -0
- package/data/harnesses/code/skills/design/data/color/accent-presets.md +90 -0
- package/data/harnesses/code/skills/design/data/color/aesop-earth-mute.md +91 -0
- package/data/harnesses/code/skills/design/data/color/arc-peach-play.md +90 -0
- package/data/harnesses/code/skills/design/data/color/attio-hunter-teal.md +90 -0
- package/data/harnesses/code/skills/design/data/color/carbon-cyan-signal.md +90 -0
- package/data/harnesses/code/skills/design/data/color/clay-parchment.md +90 -0
- package/data/harnesses/code/skills/design/data/color/duolingo-feather.md +90 -0
- package/data/harnesses/code/skills/design/data/color/geist-mono-signal.md +90 -0
- package/data/harnesses/code/skills/design/data/color/linear-graphite-violet.md +90 -0
- package/data/harnesses/code/skills/design/data/color/mercury-onyx-cobalt.md +90 -0
- package/data/harnesses/code/skills/design/data/color/notion-paper-ink.md +90 -0
- package/data/harnesses/code/skills/design/data/color/radix-slate-iris.md +90 -0
- package/data/harnesses/code/skills/design/data/color/raycast-void-spectrum.md +90 -0
- package/data/harnesses/code/skills/design/data/color/spectrum-celery-fuchsia.md +90 -0
- package/data/harnesses/code/skills/design/data/color/stripe-blurple-ruby.md +90 -0
- package/data/harnesses/code/skills/design/data/color/superhuman-cod-picton.md +89 -0
- package/data/harnesses/code/skills/design/data/corpora/operations-review/companies.csv +15 -0
- package/data/harnesses/code/skills/design/data/corpora/operations-review/corpus.md +87 -0
- package/data/harnesses/code/skills/design/data/corpora/operations-review/people.csv +25 -0
- package/data/harnesses/code/skills/design/data/corpora/procurement-approvals/companies.csv +15 -0
- package/data/harnesses/code/skills/design/data/corpora/procurement-approvals/corpus.md +83 -0
- package/data/harnesses/code/skills/design/data/corpora/procurement-approvals/people.csv +25 -0
- package/data/harnesses/code/skills/design/data/corpora/revenue-analytics/companies.csv +15 -0
- package/data/harnesses/code/skills/design/data/corpora/revenue-analytics/corpus.md +89 -0
- package/data/harnesses/code/skills/design/data/corpora/revenue-analytics/people.csv +25 -0
- package/data/harnesses/code/skills/design/data/elevation/ant-design-directional-shadow.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/apple-liquid-glass-material.md +18 -0
- package/data/harnesses/code/skills/design/data/elevation/atlassian-surface-shadow-pairing.md +20 -0
- package/data/harnesses/code/skills/design/data/elevation/fluent-2-key-ambient-ramp.md +20 -0
- package/data/harnesses/code/skills/design/data/elevation/github-primer-sparing-shadow.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/ibm-carbon-layer-and-border.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/linear-dark-surface-ladder.md +17 -0
- package/data/harnesses/code/skills/design/data/elevation/material-2-key-ambient-dp-ramp.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/material-3-tonal-elevation.md +21 -0
- package/data/harnesses/code/skills/design/data/elevation/neobrutalist-hard-offset.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/neumorphic-soft-dual-shadow.md +18 -0
- package/data/harnesses/code/skills/design/data/elevation/notion-warm-minimal-hairline.md +17 -0
- package/data/harnesses/code/skills/design/data/elevation/polaris-elevation-inset-bevel.md +21 -0
- package/data/harnesses/code/skills/design/data/elevation/radix-themes-alpha-ring-scale.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/shadcn-neutral-derived-radius.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/stripe-brand-tinted-dual-shadow.md +18 -0
- package/data/harnesses/code/skills/design/data/elevation/tailwind-default-utility-scale.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/vercel-geist-shadow-as-border.md +20 -0
- package/data/harnesses/code/skills/design/data/gradients/amber-attention-fill.md +16 -0
- package/data/harnesses/code/skills/design/data/gradients/cloud-shadow-wash.md +16 -0
- package/data/harnesses/code/skills/design/data/gradients/confident-lavender-wash.md +16 -0
- package/data/harnesses/code/skills/design/data/gradients/ember-ship-flare.md +18 -0
- package/data/harnesses/code/skills/design/data/gradients/full-metal-wash.md +16 -0
- package/data/harnesses/code/skills/design/data/gradients/indigo-ruby-cta-band.md +18 -0
- package/data/harnesses/code/skills/design/data/gradients/linear-graphite-wash.md +18 -0
- package/data/harnesses/code/skills/design/data/gradients/marble-quartz-wash.md +16 -0
- package/data/harnesses/code/skills/design/data/gradients/midnight-aurora-panel.md +16 -0
- package/data/harnesses/code/skills/design/data/gradients/neutral-volume-fill.md +16 -0
- package/data/harnesses/code/skills/design/data/gradients/revenue-uptrend-fill.md +16 -0
- package/data/harnesses/code/skills/design/data/gradients/risk-decline-fill.md +16 -0
- package/data/harnesses/code/skills/design/data/gradients/signal-blue-develop.md +18 -0
- package/data/harnesses/code/skills/design/data/gradients/slate-mist-wash.md +16 -0
- package/data/harnesses/code/skills/design/data/gradients/stripe-atmosphere-mesh.md +18 -0
- package/data/harnesses/code/skills/design/data/gradients/teal-signal-accent.md +16 -0
- package/data/harnesses/code/skills/design/data/gradients/violet-magenta-promo.md +16 -0
- package/data/harnesses/code/skills/design/data/iconography.md +199 -0
- package/data/harnesses/code/skills/design/data/motion/accordion-expand.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/destructive-hold-confirm.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/drag-dismiss-momentum.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/empty-state-arrive.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/hover-lift.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/modal-center-arrive.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/number-ticker-roll.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/onboarding-bounce.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/panel-slide.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/press-feedback.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/progress-linear-loop.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/route-crossfade.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/rubber-band-overscroll.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/skeleton-shimmer.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/stagger-arrive.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/toast-enter-stack.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/tooltip-chain-instant.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/trigger-anchored-reveal.md +18 -0
- package/data/harnesses/code/skills/design/data/tells.csv +34 -0
- package/data/harnesses/code/skills/design/data/typography/anton-work-sans.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/archivo-black-archivo.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/bitter-karla.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/dm-serif-dm-sans.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/fraunces-inter.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/geist-geist-mono.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/hubot-sans-mona-sans.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/ibm-plex-sans-operator-single-family.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/instrument-serif-instrument-sans.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/inter-operator-single-family.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/libre-baskerville-libre-franklin.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/newsreader-inter.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/playfair-display-source-sans.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/public-sans-operator-single-family.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/sohne-inter-fintech.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/source-serif-source-sans.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/space-grotesk-work-sans-space-mono.md +17 -0
- package/data/harnesses/code/skills/design/data/typography/spectral-manrope.md +17 -0
- package/data/harnesses/code/skills/design/data/voices/bright-collaborator.md +29 -0
- package/data/harnesses/code/skills/design/data/voices/direct-operator.md +29 -0
- package/data/harnesses/code/skills/design/data/voices/earnest-helper.md +29 -0
- package/data/harnesses/code/skills/design/data/voices/editorial-composed.md +29 -0
- package/data/harnesses/code/skills/design/data/voices/quiet-concierge.md +29 -0
- package/data/harnesses/code/skills/design/data/voices/warm-guide.md +29 -0
- package/data/harnesses/code/skills/design/design-md/SCHEMA.md +179 -0
- package/data/harnesses/code/skills/design/design-md/index.csv +67 -0
- package/data/harnesses/code/skills/design/design-md/library/airbnb.md +207 -0
- package/data/harnesses/code/skills/design/design-md/library/airtable.md +205 -0
- package/data/harnesses/code/skills/design/design-md/library/anthropic-claude.md +203 -0
- package/data/harnesses/code/skills/design/design-md/library/apple.md +206 -0
- package/data/harnesses/code/skills/design/design-md/library/binance.md +210 -0
- package/data/harnesses/code/skills/design/design-md/library/bmw.md +210 -0
- package/data/harnesses/code/skills/design/design-md/library/cal.md +209 -0
- package/data/harnesses/code/skills/design/design-md/library/clay.md +204 -0
- package/data/harnesses/code/skills/design/design-md/library/clickhouse.md +205 -0
- package/data/harnesses/code/skills/design/design-md/library/cohere.md +205 -0
- package/data/harnesses/code/skills/design/design-md/library/coinbase.md +198 -0
- package/data/harnesses/code/skills/design/design-md/library/composio.md +198 -0
- package/data/harnesses/code/skills/design/design-md/library/cursor.md +197 -0
- package/data/harnesses/code/skills/design/design-md/library/elevenlabs.md +199 -0
- package/data/harnesses/code/skills/design/design-md/library/expo.md +200 -0
- package/data/harnesses/code/skills/design/design-md/library/figma.md +199 -0
- package/data/harnesses/code/skills/design/design-md/library/framer.md +202 -0
- package/data/harnesses/code/skills/design/design-md/library/hashicorp.md +205 -0
- package/data/harnesses/code/skills/design/design-md/library/ibm.md +205 -0
- package/data/harnesses/code/skills/design/design-md/library/intercom.md +204 -0
- package/data/harnesses/code/skills/design/design-md/library/kraken.md +200 -0
- package/data/harnesses/code/skills/design/design-md/library/linear-app.md +198 -0
- package/data/harnesses/code/skills/design/design-md/library/lovable.md +199 -0
- package/data/harnesses/code/skills/design/design-md/library/meta.md +204 -0
- package/data/harnesses/code/skills/design/design-md/library/minimax.md +204 -0
- package/data/harnesses/code/skills/design/design-md/library/mintlify.md +210 -0
- package/data/harnesses/code/skills/design/design-md/library/miro.md +209 -0
- package/data/harnesses/code/skills/design/design-md/library/mistral-ai.md +201 -0
- package/data/harnesses/code/skills/design/design-md/library/mongodb.md +201 -0
- package/data/harnesses/code/skills/design/design-md/library/notion.md +204 -0
- package/data/harnesses/code/skills/design/design-md/library/nvidia.md +215 -0
- package/data/harnesses/code/skills/design/design-md/library/ollama.md +213 -0
- package/data/harnesses/code/skills/design/design-md/library/opencode-ai.md +216 -0
- package/data/harnesses/code/skills/design/design-md/library/posthog.md +218 -0
- package/data/harnesses/code/skills/design/design-md/library/raycast.md +214 -0
- package/data/harnesses/code/skills/design/design-md/library/replicate.md +202 -0
- package/data/harnesses/code/skills/design/design-md/library/resend.md +203 -0
- package/data/harnesses/code/skills/design/design-md/library/revolut.md +203 -0
- package/data/harnesses/code/skills/design/design-md/library/runwayml.md +203 -0
- package/data/harnesses/code/skills/design/design-md/library/sanity.md +200 -0
- package/data/harnesses/code/skills/design/design-md/library/sentry.md +201 -0
- package/data/harnesses/code/skills/design/design-md/library/shopify.md +202 -0
- package/data/harnesses/code/skills/design/design-md/library/slack.md +202 -0
- package/data/harnesses/code/skills/design/design-md/library/spacex.md +206 -0
- package/data/harnesses/code/skills/design/design-md/library/spotify.md +206 -0
- package/data/harnesses/code/skills/design/design-md/library/stripe.md +196 -0
- package/data/harnesses/code/skills/design/design-md/library/supabase.md +196 -0
- package/data/harnesses/code/skills/design/design-md/library/superhuman.md +194 -0
- package/data/harnesses/code/skills/design/design-md/library/tesla.md +198 -0
- package/data/harnesses/code/skills/design/design-md/library/theverge.md +199 -0
- package/data/harnesses/code/skills/design/design-md/library/together-ai.md +193 -0
- package/data/harnesses/code/skills/design/design-md/library/uber.md +195 -0
- package/data/harnesses/code/skills/design/design-md/library/vercel.md +195 -0
- package/data/harnesses/code/skills/design/design-md/library/voltagent.md +197 -0
- package/data/harnesses/code/skills/design/design-md/library/warp.md +196 -0
- package/data/harnesses/code/skills/design/design-md/library/webflow.md +197 -0
- package/data/harnesses/code/skills/design/design-md/library/wired.md +197 -0
- package/data/harnesses/code/skills/design/design-md/library/wise.md +198 -0
- package/data/harnesses/code/skills/design/design-md/library/x-ai.md +199 -0
- package/data/harnesses/code/skills/design/design-md/library/zapier.md +198 -0
- package/data/harnesses/code/skills/design/design-md/nurix.md +214 -0
- package/data/harnesses/code/skills/design/design-md/variants/editorial-luxury.md +184 -0
- package/data/harnesses/code/skills/design/design-md/variants/expressive-saas.md +216 -0
- package/data/harnesses/code/skills/design/design-md/variants/quiet-premium.md +211 -0
- package/data/harnesses/code/skills/design/design-md/variants/terse-dense-operator.md +212 -0
- package/data/harnesses/code/skills/design/design-md/variants/warm-approachable.md +216 -0
- package/data/harnesses/code/skills/design/references/compose.md +23 -0
- package/data/harnesses/code/skills/design/references/content.md +28 -0
- package/data/harnesses/code/skills/design/references/emission.md +21 -0
- package/data/harnesses/code/skills/design/references/existing-apps.md +27 -0
- package/data/harnesses/code/skills/design/references/intake.md +36 -0
- package/data/harnesses/code/skills/design/references/object-pass.md +24 -0
- package/data/harnesses/code/skills/design/references/ship-gate.md +29 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/Readme.md +195 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/ai-layer.md +49 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/attributes-lifecycle.md +43 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/container-choice.md +44 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/creation-paths.md +46 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/dashboards.md +42 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/modes.md +42 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/object-model.md +46 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/page-archetype.md +48 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/promotion-demotion.md +37 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/settings.md +42 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/standard-pages.md +46 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/state-matrix.md +41 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/table-mechanics.md +48 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/verbs.md +45 -0
- package/data/harnesses/code/skills/design/references/ux-patterns/viewer-context.md +40 -0
- package/data/harnesses/code/skills/design/scripts/README-validator.md +27 -0
- package/data/harnesses/code/skills/design/scripts/select-chart.mjs +596 -0
- package/data/harnesses/code/skills/design/scripts/validate-design-md.mjs +1198 -0
- package/package.json +1 -1
- package/data/harnesses/code/skills/design/data/catalog/rules.csv +0 -137
- package/data/harnesses/code/skills/design/data/combinations/Readme.md +0 -33
- package/data/harnesses/code/skills/design/data/combinations/accent-presets.md +0 -113
- package/data/harnesses/code/skills/design/data/combinations/charts.csv +0 -33
- package/data/harnesses/code/skills/design/data/combinations/fonts-known.json +0 -448
- package/data/harnesses/code/skills/design/data/combinations/icons.csv +0 -157
- package/data/harnesses/code/skills/design/data/combinations/landing.csv +0 -38
- package/data/harnesses/code/skills/design/data/combinations/lucide-names.json +0 -2001
- package/data/harnesses/code/skills/design/data/combinations/manifest.json +0 -330
- package/data/harnesses/code/skills/design/data/combinations/motion.csv +0 -10
- package/data/harnesses/code/skills/design/data/combinations/palettes.csv +0 -92
- package/data/harnesses/code/skills/design/data/combinations/product-router.csv +0 -80
- package/data/harnesses/code/skills/design/data/combinations/styles.csv +0 -67
- package/data/harnesses/code/skills/design/data/combinations/typography.csv +0 -64
- package/data/harnesses/code/skills/design/data/design-md/SCHEMA.md +0 -106
- package/data/harnesses/code/skills/design/data/design-md/library/airbnb.md +0 -154
- package/data/harnesses/code/skills/design/data/design-md/library/airtable.md +0 -151
- package/data/harnesses/code/skills/design/data/design-md/library/anthropic-claude.md +0 -139
- package/data/harnesses/code/skills/design/data/design-md/library/apple.md +0 -139
- package/data/harnesses/code/skills/design/data/design-md/library/binance.md +0 -140
- package/data/harnesses/code/skills/design/data/design-md/library/bmw.md +0 -140
- package/data/harnesses/code/skills/design/data/design-md/library/cal.md +0 -140
- package/data/harnesses/code/skills/design/data/design-md/library/clay.md +0 -139
- package/data/harnesses/code/skills/design/data/design-md/library/clickhouse.md +0 -139
- package/data/harnesses/code/skills/design/data/design-md/library/cohere.md +0 -147
- package/data/harnesses/code/skills/design/data/design-md/library/coinbase.md +0 -156
- package/data/harnesses/code/skills/design/data/design-md/library/composio.md +0 -138
- package/data/harnesses/code/skills/design/data/design-md/library/cursor.md +0 -138
- package/data/harnesses/code/skills/design/data/design-md/library/elevenlabs.md +0 -137
- package/data/harnesses/code/skills/design/data/design-md/library/expo.md +0 -148
- package/data/harnesses/code/skills/design/data/design-md/library/figma.md +0 -141
- package/data/harnesses/code/skills/design/data/design-md/library/framer.md +0 -156
- package/data/harnesses/code/skills/design/data/design-md/library/hashicorp.md +0 -140
- package/data/harnesses/code/skills/design/data/design-md/library/ibm.md +0 -153
- package/data/harnesses/code/skills/design/data/design-md/library/intercom.md +0 -158
- package/data/harnesses/code/skills/design/data/design-md/library/kraken.md +0 -156
- package/data/harnesses/code/skills/design/data/design-md/library/linear-app.md +0 -122
- package/data/harnesses/code/skills/design/data/design-md/library/lovable.md +0 -140
- package/data/harnesses/code/skills/design/data/design-md/library/meta.md +0 -139
- package/data/harnesses/code/skills/design/data/design-md/library/minimax.md +0 -139
- package/data/harnesses/code/skills/design/data/design-md/library/mintlify.md +0 -139
- package/data/harnesses/code/skills/design/data/design-md/library/miro.md +0 -153
- package/data/harnesses/code/skills/design/data/design-md/library/mistral-ai.md +0 -140
- package/data/harnesses/code/skills/design/data/design-md/library/mongodb.md +0 -155
- package/data/harnesses/code/skills/design/data/design-md/library/notion.md +0 -140
- package/data/harnesses/code/skills/design/data/design-md/library/nvidia.md +0 -141
- package/data/harnesses/code/skills/design/data/design-md/library/ollama.md +0 -145
- package/data/harnesses/code/skills/design/data/design-md/library/opencode-ai.md +0 -142
- package/data/harnesses/code/skills/design/data/design-md/library/posthog.md +0 -152
- package/data/harnesses/code/skills/design/data/design-md/library/raycast.md +0 -137
- package/data/harnesses/code/skills/design/data/design-md/library/replicate.md +0 -140
- package/data/harnesses/code/skills/design/data/design-md/library/resend.md +0 -170
- package/data/harnesses/code/skills/design/data/design-md/library/revolut.md +0 -145
- package/data/harnesses/code/skills/design/data/design-md/library/runwayml.md +0 -137
- package/data/harnesses/code/skills/design/data/design-md/library/sanity.md +0 -168
- package/data/harnesses/code/skills/design/data/design-md/library/sentry.md +0 -163
- package/data/harnesses/code/skills/design/data/design-md/library/shopify.md +0 -147
- package/data/harnesses/code/skills/design/data/design-md/library/slack.md +0 -158
- package/data/harnesses/code/skills/design/data/design-md/library/spacex.md +0 -171
- package/data/harnesses/code/skills/design/data/design-md/library/spotify.md +0 -166
- package/data/harnesses/code/skills/design/data/design-md/library/stripe.md +0 -160
- package/data/harnesses/code/skills/design/data/design-md/library/supabase.md +0 -173
- package/data/harnesses/code/skills/design/data/design-md/library/superhuman.md +0 -145
- package/data/harnesses/code/skills/design/data/design-md/library/tesla.md +0 -153
- package/data/harnesses/code/skills/design/data/design-md/library/theverge.md +0 -167
- package/data/harnesses/code/skills/design/data/design-md/library/together-ai.md +0 -140
- package/data/harnesses/code/skills/design/data/design-md/library/uber.md +0 -140
- package/data/harnesses/code/skills/design/data/design-md/library/vercel.md +0 -140
- package/data/harnesses/code/skills/design/data/design-md/library/voltagent.md +0 -130
- package/data/harnesses/code/skills/design/data/design-md/library/warp.md +0 -140
- package/data/harnesses/code/skills/design/data/design-md/library/webflow.md +0 -140
- package/data/harnesses/code/skills/design/data/design-md/library/wired.md +0 -139
- package/data/harnesses/code/skills/design/data/design-md/library/wise.md +0 -137
- package/data/harnesses/code/skills/design/data/design-md/library/x-ai.md +0 -138
- package/data/harnesses/code/skills/design/data/design-md/library/zapier.md +0 -158
- package/data/harnesses/code/skills/design/data/design-md/nurix.md +0 -138
- package/data/harnesses/code/skills/design/data/design-tokens/SCHEMA.md +0 -695
- package/data/harnesses/code/skills/design/data/design-tokens/airbnb/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/airbnb/design.tokens.json +0 -1272
- package/data/harnesses/code/skills/design/data/design-tokens/airtable/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/airtable/design.tokens.json +0 -1268
- package/data/harnesses/code/skills/design/data/design-tokens/anthropic-claude/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/anthropic-claude/design.tokens.json +0 -1273
- package/data/harnesses/code/skills/design/data/design-tokens/apple/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/apple/design.tokens.json +0 -1262
- package/data/harnesses/code/skills/design/data/design-tokens/binance/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/binance/design.tokens.json +0 -1257
- package/data/harnesses/code/skills/design/data/design-tokens/bmw/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/bmw/design.tokens.json +0 -1270
- package/data/harnesses/code/skills/design/data/design-tokens/cal/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/cal/design.tokens.json +0 -1270
- package/data/harnesses/code/skills/design/data/design-tokens/clay/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/clay/design.tokens.json +0 -1261
- package/data/harnesses/code/skills/design/data/design-tokens/clickhouse/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/clickhouse/design.tokens.json +0 -1268
- package/data/harnesses/code/skills/design/data/design-tokens/cohere/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/cohere/design.tokens.json +0 -1281
- package/data/harnesses/code/skills/design/data/design-tokens/coinbase/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/coinbase/design.tokens.json +0 -1262
- package/data/harnesses/code/skills/design/data/design-tokens/composio/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/composio/design.tokens.json +0 -1271
- package/data/harnesses/code/skills/design/data/design-tokens/cursor/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/cursor/design.tokens.json +0 -1276
- package/data/harnesses/code/skills/design/data/design-tokens/elevenlabs/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/elevenlabs/design.tokens.json +0 -1293
- package/data/harnesses/code/skills/design/data/design-tokens/expo/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/expo/design.tokens.json +0 -1270
- package/data/harnesses/code/skills/design/data/design-tokens/figma/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/figma/design.tokens.json +0 -1276
- package/data/harnesses/code/skills/design/data/design-tokens/framer/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/framer/design.tokens.json +0 -1267
- package/data/harnesses/code/skills/design/data/design-tokens/goldens.json +0 -251
- package/data/harnesses/code/skills/design/data/design-tokens/hashicorp/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/hashicorp/design.tokens.json +0 -1273
- package/data/harnesses/code/skills/design/data/design-tokens/ibm/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/ibm/design.tokens.json +0 -1286
- package/data/harnesses/code/skills/design/data/design-tokens/intercom/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/intercom/design.tokens.json +0 -1290
- package/data/harnesses/code/skills/design/data/design-tokens/kraken/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/kraken/design.tokens.json +0 -1270
- package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.resolver.json +0 -23
- package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.tokens.dark.json +0 -599
- package/data/harnesses/code/skills/design/data/design-tokens/linear-app/design.tokens.json +0 -1272
- package/data/harnesses/code/skills/design/data/design-tokens/lovable/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/lovable/design.tokens.json +0 -1267
- package/data/harnesses/code/skills/design/data/design-tokens/meta/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/meta/design.tokens.json +0 -1289
- package/data/harnesses/code/skills/design/data/design-tokens/minimax/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/minimax/design.tokens.json +0 -1258
- package/data/harnesses/code/skills/design/data/design-tokens/mintlify/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/mintlify/design.tokens.json +0 -1284
- package/data/harnesses/code/skills/design/data/design-tokens/miro/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/miro/design.tokens.json +0 -1291
- package/data/harnesses/code/skills/design/data/design-tokens/mistral-ai/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/mistral-ai/design.tokens.json +0 -1263
- package/data/harnesses/code/skills/design/data/design-tokens/mongodb/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/mongodb/design.tokens.json +0 -1270
- package/data/harnesses/code/skills/design/data/design-tokens/notion/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/notion/design.tokens.json +0 -1261
- package/data/harnesses/code/skills/design/data/design-tokens/nurix/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/nurix/design.tokens.json +0 -1209
- package/data/harnesses/code/skills/design/data/design-tokens/nvidia/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/nvidia/design.tokens.json +0 -1265
- package/data/harnesses/code/skills/design/data/design-tokens/ollama/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/ollama/design.tokens.json +0 -1253
- package/data/harnesses/code/skills/design/data/design-tokens/opencode-ai/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/opencode-ai/design.tokens.json +0 -1268
- package/data/harnesses/code/skills/design/data/design-tokens/posthog/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/posthog/design.tokens.json +0 -1261
- package/data/harnesses/code/skills/design/data/design-tokens/raycast/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/raycast/design.tokens.json +0 -1263
- package/data/harnesses/code/skills/design/data/design-tokens/replicate/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/replicate/design.tokens.json +0 -1266
- package/data/harnesses/code/skills/design/data/design-tokens/resend/design.resolver.json +0 -23
- package/data/harnesses/code/skills/design/data/design-tokens/resend/design.tokens.dark.json +0 -623
- package/data/harnesses/code/skills/design/data/design-tokens/resend/design.tokens.json +0 -1276
- package/data/harnesses/code/skills/design/data/design-tokens/revolut/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/revolut/design.tokens.json +0 -1264
- package/data/harnesses/code/skills/design/data/design-tokens/runwayml/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/runwayml/design.tokens.json +0 -1268
- package/data/harnesses/code/skills/design/data/design-tokens/sanity/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/sanity/design.tokens.json +0 -1270
- package/data/harnesses/code/skills/design/data/design-tokens/sentry/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/sentry/design.tokens.json +0 -1288
- package/data/harnesses/code/skills/design/data/design-tokens/shopify/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/shopify/design.tokens.json +0 -1272
- package/data/harnesses/code/skills/design/data/design-tokens/slack/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/slack/design.tokens.json +0 -1265
- package/data/harnesses/code/skills/design/data/design-tokens/spacex/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/spacex/design.tokens.json +0 -1287
- package/data/harnesses/code/skills/design/data/design-tokens/spotify/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/spotify/design.tokens.json +0 -1286
- package/data/harnesses/code/skills/design/data/design-tokens/stripe/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/stripe/design.tokens.json +0 -1285
- package/data/harnesses/code/skills/design/data/design-tokens/supabase/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/supabase/design.tokens.json +0 -1287
- package/data/harnesses/code/skills/design/data/design-tokens/superhuman/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/superhuman/design.tokens.json +0 -1278
- package/data/harnesses/code/skills/design/data/design-tokens/tesla/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/tesla/design.tokens.json +0 -1269
- package/data/harnesses/code/skills/design/data/design-tokens/theverge/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/theverge/design.tokens.json +0 -1285
- package/data/harnesses/code/skills/design/data/design-tokens/together-ai/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/together-ai/design.tokens.json +0 -1303
- package/data/harnesses/code/skills/design/data/design-tokens/uber/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/uber/design.tokens.json +0 -1274
- package/data/harnesses/code/skills/design/data/design-tokens/vercel/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/vercel/design.tokens.json +0 -1296
- package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.resolver.json +0 -23
- package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.tokens.dark.json +0 -612
- package/data/harnesses/code/skills/design/data/design-tokens/voltagent/design.tokens.json +0 -1282
- package/data/harnesses/code/skills/design/data/design-tokens/warp/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/warp/design.tokens.json +0 -1287
- package/data/harnesses/code/skills/design/data/design-tokens/webflow/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/webflow/design.tokens.json +0 -1268
- package/data/harnesses/code/skills/design/data/design-tokens/wired/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/wired/design.tokens.json +0 -1276
- package/data/harnesses/code/skills/design/data/design-tokens/wise/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/wise/design.tokens.json +0 -1273
- package/data/harnesses/code/skills/design/data/design-tokens/x-ai/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/x-ai/design.tokens.json +0 -1296
- package/data/harnesses/code/skills/design/data/design-tokens/zapier/design.resolver.json +0 -20
- package/data/harnesses/code/skills/design/data/design-tokens/zapier/design.tokens.json +0 -1286
- package/data/harnesses/code/skills/design/data/operator-pack/exemplars/command-center.tsx +0 -249
- package/data/harnesses/code/skills/design/data/operator-pack/exemplars/create-edit.tsx +0 -249
- package/data/harnesses/code/skills/design/data/operator-pack/exemplars/dashboard.tsx +0 -225
- package/data/harnesses/code/skills/design/data/operator-pack/exemplars/entity-detail.tsx +0 -249
- package/data/harnesses/code/skills/design/data/operator-pack/exemplars/list-detail.tsx +0 -224
- package/data/harnesses/code/skills/design/data/operator-pack/exemplars/log-stream.tsx +0 -249
- package/data/harnesses/code/skills/design/data/operator-pack/fixtures.md +0 -88
- package/data/harnesses/code/skills/design/data/operator-pack/surface-catalog.md +0 -132
- package/data/harnesses/code/skills/design/data/operator-pack/trust-rubric.md +0 -76
- package/data/harnesses/code/skills/design/references/create.md +0 -173
- package/data/harnesses/code/skills/design/references/diagnosis.md +0 -108
- package/data/harnesses/code/skills/design/references/hard-rules.md +0 -177
- package/data/harnesses/code/skills/design/references/improve.md +0 -116
- package/data/harnesses/code/skills/design/references/interaction.md +0 -77
- package/data/harnesses/code/skills/design/references/introspect.md +0 -100
- package/data/harnesses/code/skills/design/references/lifecycle.md +0 -73
- package/data/harnesses/code/skills/design/references/personality.md +0 -575
- package/data/harnesses/code/skills/design/references/structure.md +0 -159
- package/data/harnesses/code/skills/design/scripts/convert-design-md.mjs +0 -1312
- package/data/harnesses/code/skills/design/scripts/derive-personality.mjs +0 -1498
- package/data/harnesses/code/skills/design/scripts/generate-fixtures.mjs +0 -551
- package/data/harnesses/code/skills/design/scripts/lib/checks.mjs +0 -1092
- package/data/harnesses/code/skills/design/scripts/lib/golden.mjs +0 -26
- package/data/harnesses/code/skills/design/scripts/lib/vocabulary.mjs +0 -65
- package/data/harnesses/code/skills/design/scripts/phase-gate.mjs +0 -538
- package/data/harnesses/code/skills/design/scripts/preship-sweep.mjs +0 -312
- package/data/harnesses/code/skills/design/scripts/render-rung.mjs +0 -890
- package/data/harnesses/code/skills/design/scripts/scaffold.mjs +0 -769
- package/data/harnesses/code/skills/design/scripts/self-integrity.mjs +0 -675
- package/data/harnesses/code/skills/design/scripts/snapshot-goldens.mjs +0 -164
- package/data/harnesses/code/skills/design/scripts/validate-combinations.mjs +0 -439
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
// skills/design/data/operator-pack/exemplars/list-detail.tsx — reference render — imitate the ambition and the composition; the project's tokens supply the look.
|
|
2
|
-
// Surface: baseline
|
|
3
|
-
import { useEffect, useRef, useState } from "react";
|
|
4
|
-
import { Button } from "@nurix/components/button";
|
|
5
|
-
import { Badge } from "@nurix/components/badge";
|
|
6
|
-
import { Card, CardContent } from "@nurix/components/card";
|
|
7
|
-
import { Skeleton } from "@nurix/components/skeleton";
|
|
8
|
-
import { EmptyState } from "@nurix/components/empty-state";
|
|
9
|
-
import { Alert, AlertTitle, AlertDescription } from "@nurix/components/alert";
|
|
10
|
-
import { Input } from "@nurix/components/input";
|
|
11
|
-
import { Kbd } from "@nurix/components/kbd";
|
|
12
|
-
import { Checkbox } from "@nurix/components/checkbox";
|
|
13
|
-
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetBody, SheetFooter, SheetClose } from "@nurix/components/sheet";
|
|
14
|
-
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@nurix/components/native-select";
|
|
15
|
-
import { ArrowDown, ArrowUp, CircleCheck, CircleDot, CircleX, Inbox, Plus, RefreshCw, TriangleAlert } from "lucide-react";
|
|
16
|
-
// A support-ticket queue: many rows of one entity, each with a per-row action and lifecycle (list-detail, the reference archetype).
|
|
17
|
-
type Priority = "urgent" | "high" | "normal";
|
|
18
|
-
type Status = "open" | "pending" | "resolved";
|
|
19
|
-
type LoadStatus = "ready" | "loading" | "error";
|
|
20
|
-
export interface Ticket {
|
|
21
|
-
id: string; subject: string; requester: string; priority: Priority; status: Status; assignee: string | null; slaMinutes: number; updatedAt: string;
|
|
22
|
-
}
|
|
23
|
-
export interface ListDetailProps {
|
|
24
|
-
tickets: Ticket[]; status?: LoadStatus; stale?: boolean; errorDetail?: string;
|
|
25
|
-
onRetry?: () => void; onNew?: () => void; onResolve?: (id: string) => void; onAssignSelected?: (ids: string[]) => void;
|
|
26
|
-
}
|
|
27
|
-
const PAGE_SIZE = 8;
|
|
28
|
-
const STATUS_META: Record<Status, { label: string; cls: string; Icon: typeof CircleDot }> = {
|
|
29
|
-
open: { label: "Open", cls: "text-warning", Icon: CircleDot },
|
|
30
|
-
pending: { label: "Pending", cls: "text-muted-foreground", Icon: CircleDot },
|
|
31
|
-
resolved: { label: "Resolved", cls: "text-success", Icon: CircleCheck },
|
|
32
|
-
};
|
|
33
|
-
const PRIORITY_META: Record<Priority, { label: string; cls: string }> = {
|
|
34
|
-
urgent: { label: "Urgent", cls: "text-destructive" }, high: { label: "High", cls: "text-warning" }, normal: { label: "Normal", cls: "text-muted-foreground" },
|
|
35
|
-
};
|
|
36
|
-
// Loading shell mirrors the toolbar and table rows, never a centered spinner.
|
|
37
|
-
function QueueSkeleton() {
|
|
38
|
-
return (
|
|
39
|
-
<div className="min-h-[100dvh] bg-background p-6 font-sans">
|
|
40
|
-
<div className="flex items-center gap-3"><Skeleton className="h-8 w-64" /><Skeleton className="h-8 w-32" /></div>
|
|
41
|
-
<div className="mt-6 flex flex-col gap-2">{Array.from({ length: 8 }).map((_, i) => <Skeleton key={i} className="h-11 w-full" />)}</div>
|
|
42
|
-
</div>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
// Queue load failure states what happened, why, and the recovery path. Never a dead end.
|
|
46
|
-
function QueueError({ detail, onRetry }: { detail?: string; onRetry?: () => void }) {
|
|
47
|
-
return (
|
|
48
|
-
<div className="flex min-h-[100dvh] items-center justify-center bg-background p-6 font-sans">
|
|
49
|
-
<Alert variant="destructive" className="max-w-md">
|
|
50
|
-
<CircleX className="h-4 w-4" />
|
|
51
|
-
<AlertTitle>Could not load the ticket queue</AlertTitle>
|
|
52
|
-
<AlertDescription>{detail ?? "The queue service did not respond."} Retry the request, or open the status page if it keeps failing.</AlertDescription>
|
|
53
|
-
<div className="mt-3"><Button size="sm" onClick={onRetry}><RefreshCw className="h-4 w-4" /> Retry</Button></div>
|
|
54
|
-
</Alert>
|
|
55
|
-
</div>
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
// The selected row projects into a side panel beside the table, never a modal (f03); the panel
|
|
59
|
-
// rides the elevation.overlay + surface-overlay ladder so it reads as the topmost layer.
|
|
60
|
-
function TicketDetail({ ticket, open, onOpenChange, onResolve }: { ticket: Ticket | null; open: boolean; onOpenChange: (v: boolean) => void; onResolve?: (id: string) => void }) {
|
|
61
|
-
if (!ticket) return null;
|
|
62
|
-
const st = STATUS_META[ticket.status];
|
|
63
|
-
const rows: { label: string; value: string; mono?: boolean; num?: boolean }[] = [
|
|
64
|
-
{ label: "Ticket", value: ticket.id, mono: true }, { label: "Requester", value: ticket.requester },
|
|
65
|
-
{ label: "Priority", value: PRIORITY_META[ticket.priority].label }, { label: "Assignee", value: ticket.assignee ?? "Unassigned" },
|
|
66
|
-
{ label: "SLA remaining", value: `${ticket.slaMinutes} min`, num: true }, { label: "Updated", value: new Date(ticket.updatedAt).toLocaleString(), num: true },
|
|
67
|
-
];
|
|
68
|
-
return (
|
|
69
|
-
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
70
|
-
<SheetContent side="right" className="bg-surface-overlay shadow-overlay">
|
|
71
|
-
<SheetHeader>
|
|
72
|
-
<SheetTitle className="text-h3">{ticket.subject}</SheetTitle>
|
|
73
|
-
<SheetDescription className={`text-small ${st.cls}`}>{st.label} · {ticket.id}</SheetDescription>
|
|
74
|
-
</SheetHeader>
|
|
75
|
-
<SheetBody>
|
|
76
|
-
<dl className="divide-y divide-border">
|
|
77
|
-
{rows.map((r) => (
|
|
78
|
-
<div key={r.label} className="flex items-center justify-between gap-4 py-3 text-small">
|
|
79
|
-
<dt className="text-muted-foreground">{r.label}</dt>
|
|
80
|
-
<dd className={`${r.mono ? "font-mono" : ""} ${r.num ? "tabular-nums" : ""}`}>{r.value}</dd>
|
|
81
|
-
</div>
|
|
82
|
-
))}
|
|
83
|
-
</dl>
|
|
84
|
-
</SheetBody>
|
|
85
|
-
<SheetFooter>
|
|
86
|
-
<SheetClose asChild><Button variant="secondary" size="sm">Close</Button></SheetClose>
|
|
87
|
-
<Button size="sm" disabled={ticket.status === "resolved"} onClick={() => onResolve?.(ticket.id)}><CircleCheck className="h-4 w-4" /> Resolve</Button>
|
|
88
|
-
</SheetFooter>
|
|
89
|
-
</SheetContent>
|
|
90
|
-
</Sheet>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
// Ticket queue: toolbar (search, filter, bulk action) + a keyboard-navigable table + a side-panel detail.
|
|
94
|
-
export function ListDetail(props: ListDetailProps) {
|
|
95
|
-
const { tickets, status = "ready", stale = false, errorDetail, onRetry, onNew, onResolve, onAssignSelected } = props;
|
|
96
|
-
const [query, setQuery] = useState("");
|
|
97
|
-
const [statusFilter, setStatusFilter] = useState<"all" | Status>("all");
|
|
98
|
-
const [sortDesc, setSortDesc] = useState(true);
|
|
99
|
-
const [page, setPage] = useState(0);
|
|
100
|
-
const [selected, setSelected] = useState<Set<string>>(new Set());
|
|
101
|
-
const [activeIndex, setActiveIndex] = useState(0);
|
|
102
|
-
const [openId, setOpenId] = useState<string | null>(null);
|
|
103
|
-
const rowRefs = useRef<(HTMLTableRowElement | null)[]>([]);
|
|
104
|
-
const filtered = tickets
|
|
105
|
-
.filter((t) => (statusFilter === "all" || t.status === statusFilter) && (query === "" || t.subject.toLowerCase().includes(query.toLowerCase()) || t.id.toLowerCase().includes(query.toLowerCase())))
|
|
106
|
-
.sort((a, b) => (sortDesc ? 1 : -1) * (new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()));
|
|
107
|
-
const pageCount = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
|
|
108
|
-
const pageRows = filtered.slice(page * PAGE_SIZE, page * PAGE_SIZE + PAGE_SIZE);
|
|
109
|
-
// Roving-tabindex keyboard travel: j/k or arrows move the active row, Enter opens its detail panel.
|
|
110
|
-
useEffect(() => { rowRefs.current[activeIndex]?.focus(); }, [activeIndex, page]);
|
|
111
|
-
function onRowsKeyDown(e: React.KeyboardEvent) {
|
|
112
|
-
if (e.key === "j" || e.key === "ArrowDown") { e.preventDefault(); setActiveIndex((i) => Math.min(i + 1, pageRows.length - 1)); }
|
|
113
|
-
else if (e.key === "k" || e.key === "ArrowUp") { e.preventDefault(); setActiveIndex((i) => Math.max(i - 1, 0)); }
|
|
114
|
-
else if (e.key === "Enter" && pageRows[activeIndex]) { e.preventDefault(); setOpenId(pageRows[activeIndex].id); }
|
|
115
|
-
}
|
|
116
|
-
function toggle(id: string) { setSelected((s) => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n; }); }
|
|
117
|
-
const allOnPage = pageRows.length > 0 && pageRows.every((t) => selected.has(t.id));
|
|
118
|
-
if (status === "loading") return <QueueSkeleton />;
|
|
119
|
-
if (status === "error") return <QueueError detail={errorDetail} onRetry={onRetry} />;
|
|
120
|
-
const start = filtered.length === 0 ? 0 : page * PAGE_SIZE + 1;
|
|
121
|
-
const end = Math.min(filtered.length, (page + 1) * PAGE_SIZE);
|
|
122
|
-
return (
|
|
123
|
-
<div className="min-h-[100dvh] bg-background font-sans text-foreground">
|
|
124
|
-
<header className="flex flex-wrap items-center justify-between gap-3 border-b border-border p-6">
|
|
125
|
-
<div><h1 className="text-h1">Ticket queue</h1><p className="mt-1 text-small text-muted-foreground">Scan, triage, and resolve support tickets.</p></div>
|
|
126
|
-
<Button size="sm" onClick={onNew}><Plus className="h-4 w-4" /> New ticket <Kbd className="ml-1">N</Kbd></Button>
|
|
127
|
-
</header>
|
|
128
|
-
<div className="flex flex-wrap items-center gap-2 border-b border-border px-6 py-3">
|
|
129
|
-
<Input type="search" aria-label="Search tickets by subject or id" placeholder="Search tickets" value={query} onChange={(e) => { setQuery(e.target.value); setPage(0); }} className="max-w-xs" />
|
|
130
|
-
<Select value={statusFilter} onValueChange={(v) => { setStatusFilter(v as "all" | Status); setPage(0); }}>
|
|
131
|
-
<SelectTrigger className="w-40" aria-label="Filter by status"><SelectValue /></SelectTrigger>
|
|
132
|
-
<SelectContent><SelectItem value="all">All statuses</SelectItem><SelectItem value="open">Open</SelectItem><SelectItem value="pending">Pending</SelectItem><SelectItem value="resolved">Resolved</SelectItem></SelectContent>
|
|
133
|
-
</Select>
|
|
134
|
-
{selected.size > 0 && <Button variant="secondary" size="sm" onClick={() => { onAssignSelected?.([...selected]); setSelected(new Set()); }}>Assign {selected.size} to me</Button>}
|
|
135
|
-
</div>
|
|
136
|
-
{stale && (
|
|
137
|
-
<Alert className="mx-6 mt-4">
|
|
138
|
-
<TriangleAlert className="h-4 w-4 text-warning" />
|
|
139
|
-
<AlertTitle>Showing a cached snapshot</AlertTitle>
|
|
140
|
-
<AlertDescription>The live queue feed is unreachable, so SLA timers and statuses below may be stale until it reconnects.</AlertDescription>
|
|
141
|
-
</Alert>
|
|
142
|
-
)}
|
|
143
|
-
<div className="p-6">
|
|
144
|
-
{filtered.length === 0 ? (
|
|
145
|
-
tickets.length === 0 ? (
|
|
146
|
-
<EmptyState icon={<Inbox className="h-10 w-10 text-muted-foreground" />} title="No tickets yet"
|
|
147
|
-
description="When requesters open tickets they land here, newest first. Create one to see how the queue reads."
|
|
148
|
-
action={<Button size="sm" onClick={onNew}><Plus className="h-4 w-4" /> New ticket</Button>} />
|
|
149
|
-
) : (
|
|
150
|
-
<EmptyState icon={<Inbox className="h-10 w-10 text-muted-foreground" />} title="No tickets match these filters"
|
|
151
|
-
description="No open ticket matches your search and status filter. Clear them to see the full queue."
|
|
152
|
-
action={<Button size="sm" variant="secondary" onClick={() => { setQuery(""); setStatusFilter("all"); }}>Clear filters</Button>} />
|
|
153
|
-
)
|
|
154
|
-
) : (
|
|
155
|
-
<Card><CardContent className="p-0">
|
|
156
|
-
<div className="overflow-auto" onKeyDown={onRowsKeyDown}>
|
|
157
|
-
<table className="w-full border-collapse text-small">
|
|
158
|
-
<thead className="sticky top-0 bg-card">
|
|
159
|
-
<tr className="border-b border-border text-left text-caption text-muted-foreground">
|
|
160
|
-
<th scope="col" className="w-10 px-4 py-2"><Checkbox checked={allOnPage} aria-label="Select all on this page" onCheckedChange={() => setSelected((s) => { const n = new Set(s); allOnPage ? pageRows.forEach((t) => n.delete(t.id)) : pageRows.forEach((t) => n.add(t.id)); return n; })} /></th>
|
|
161
|
-
<th scope="col" className="px-4 py-2 font-medium">Ticket</th>
|
|
162
|
-
<th scope="col" className="px-4 py-2 font-medium">Subject</th>
|
|
163
|
-
<th scope="col" className="px-4 py-2 font-medium">Priority</th>
|
|
164
|
-
<th scope="col" className="px-4 py-2 font-medium">Status</th>
|
|
165
|
-
<th scope="col" className="px-4 py-2 text-right font-medium tabular-nums">SLA</th>
|
|
166
|
-
<th scope="col" aria-sort={sortDesc ? "descending" : "ascending"} className="px-4 py-2 text-right font-medium">
|
|
167
|
-
<button type="button" onClick={() => setSortDesc((v) => !v)} className="ml-auto inline-flex items-center gap-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">Updated {sortDesc ? <ArrowDown className="h-3.5 w-3.5" /> : <ArrowUp className="h-3.5 w-3.5" />}</button>
|
|
168
|
-
</th>
|
|
169
|
-
<th scope="col" className="px-4 py-2 text-right font-medium">Action</th>
|
|
170
|
-
</tr>
|
|
171
|
-
</thead>
|
|
172
|
-
<tbody>
|
|
173
|
-
{pageRows.map((t, i) => {
|
|
174
|
-
const st = STATUS_META[t.status];
|
|
175
|
-
return (
|
|
176
|
-
<tr key={t.id} ref={(el) => { rowRefs.current[i] = el; }} tabIndex={i === activeIndex ? 0 : -1} aria-selected={selected.has(t.id)}
|
|
177
|
-
onClick={() => { setActiveIndex(i); setOpenId(t.id); }}
|
|
178
|
-
className="motion-lift cursor-pointer border-b border-border last:border-0 hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring">
|
|
179
|
-
<td className="px-4 py-2" onClick={(e) => e.stopPropagation()}><Checkbox checked={selected.has(t.id)} aria-label={`Select ${t.id}`} onCheckedChange={() => toggle(t.id)} /></td>
|
|
180
|
-
<td className="px-4 py-2 font-mono tabular-nums">{t.id}</td>
|
|
181
|
-
<td className="max-w-xs truncate px-4 py-2">{t.subject}</td>
|
|
182
|
-
<td className={`px-4 py-2 ${PRIORITY_META[t.priority].cls}`}>{PRIORITY_META[t.priority].label}</td>
|
|
183
|
-
<td className="px-4 py-2"><Badge variant="outline" className={`gap-1 ${st.cls}`}><st.Icon className="h-3.5 w-3.5" /> {st.label}</Badge></td>
|
|
184
|
-
<td className="px-4 py-2 text-right font-mono tabular-nums">{t.slaMinutes}</td>
|
|
185
|
-
<td className="px-4 py-2 text-right font-mono tabular-nums text-muted-foreground">{new Date(t.updatedAt).toLocaleDateString()}</td>
|
|
186
|
-
<td className="px-4 py-2 text-right" onClick={(e) => e.stopPropagation()}>
|
|
187
|
-
<Button size="sm" variant="ghost" disabled={t.status === "resolved"} onClick={() => onResolve?.(t.id)}>Resolve</Button>
|
|
188
|
-
</td>
|
|
189
|
-
</tr>
|
|
190
|
-
);
|
|
191
|
-
})}
|
|
192
|
-
</tbody>
|
|
193
|
-
</table>
|
|
194
|
-
</div>
|
|
195
|
-
<div className="flex items-center justify-between gap-3 border-t border-border px-4 py-3 text-caption text-muted-foreground">
|
|
196
|
-
<span className="tabular-nums">{start}-{end} of {filtered.length}</span>
|
|
197
|
-
<div className="flex items-center gap-2">
|
|
198
|
-
<Button size="sm" variant="secondary" disabled={page === 0} onClick={() => setPage((p) => Math.max(0, p - 1))}>Previous</Button>
|
|
199
|
-
<span className="tabular-nums">Page {page + 1} of {pageCount}</span>
|
|
200
|
-
<Button size="sm" variant="secondary" disabled={page + 1 >= pageCount} onClick={() => setPage((p) => Math.min(pageCount - 1, p + 1))}>Next</Button>
|
|
201
|
-
</div>
|
|
202
|
-
</div>
|
|
203
|
-
</CardContent></Card>
|
|
204
|
-
)}
|
|
205
|
-
</div>
|
|
206
|
-
<TicketDetail ticket={filtered.find((t) => t.id === openId) ?? null} open={openId !== null} onOpenChange={(v) => setOpenId(v ? openId : null)} onResolve={onResolve} />
|
|
207
|
-
</div>
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
// exemplar fixture data — seeded, domain-shaped
|
|
211
|
-
export const FIXTURE_SEED = 5120;
|
|
212
|
-
export const tickets: Ticket[] = [
|
|
213
|
-
{ id: "TCK-4821", subject: "Checkout returns 500 on saved cards", requester: "m.okonkwo", priority: "urgent", status: "open", assignee: null, slaMinutes: 12, updatedAt: "2026-07-04T09:41:00Z" },
|
|
214
|
-
{ id: "TCK-4820", subject: "SSO redirect loops for the eu tenant on first login of the day", requester: "priya.nair", priority: "high", status: "pending", assignee: "s.delacroix", slaMinutes: 48, updatedAt: "2026-07-04T09:12:00Z" },
|
|
215
|
-
{ id: "TCK-4816", subject: "Export CSV truncates at 10k rows", requester: "j.watanabe", priority: "normal", status: "open", assignee: "a.novak", slaMinutes: 220, updatedAt: "2026-07-04T08:55:00Z" },
|
|
216
|
-
{ id: "TCK-4814", subject: "Webhook retries stop after one 429", requester: "l.fernandez", priority: "high", status: "open", assignee: null, slaMinutes: 33, updatedAt: "2026-07-04T08:20:00Z" },
|
|
217
|
-
{ id: "TCK-4809", subject: "Timezone off by one hour in the audit log", requester: "d.abiodun", priority: "normal", status: "pending", assignee: "s.delacroix", slaMinutes: 640, updatedAt: "2026-07-03T22:05:00Z" },
|
|
218
|
-
{ id: "TCK-4802", subject: "Rate-limit banner never clears", requester: "h.ito", priority: "normal", status: "resolved", assignee: "a.novak", slaMinutes: 0, updatedAt: "2026-07-03T19:48:00Z" },
|
|
219
|
-
{ id: "TCK-4799", subject: "Bulk assign silently drops the last selection", requester: "m.okonkwo", priority: "high", status: "open", assignee: null, slaMinutes: 90, updatedAt: "2026-07-03T18:31:00Z" },
|
|
220
|
-
{ id: "TCK-4790", subject: "Search ignores the status filter on page two", requester: "priya.nair", priority: "normal", status: "pending", assignee: "a.novak", slaMinutes: 410, updatedAt: "2026-07-03T16:02:00Z" },
|
|
221
|
-
{ id: "TCK-4781", subject: "Session expires mid-form and loses entered values", requester: "j.watanabe", priority: "urgent", status: "open", assignee: "s.delacroix", slaMinutes: 5, updatedAt: "2026-07-03T14:44:00Z" },
|
|
222
|
-
{ id: "TCK-4778", subject: "Column picker resets on reload", requester: "l.fernandez", priority: "normal", status: "resolved", assignee: "a.novak", slaMinutes: 0, updatedAt: "2026-07-03T11:19:00Z" },
|
|
223
|
-
];
|
|
224
|
-
export const emptyTickets: Ticket[] = [];
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
// skills/design/data/operator-pack/exemplars/log-stream.tsx — reference render — imitate the ambition and the composition; the project's tokens supply the look.
|
|
2
|
-
// Surface: baseline
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { ScrollText, Search, ChevronRight, ChevronDown, Circle, TriangleAlert, WifiOff, RotateCw, ListFilter, Inbox } from "lucide-react";
|
|
5
|
-
import { Button } from "@nurix/components/button";
|
|
6
|
-
import { Input } from "@nurix/components/input";
|
|
7
|
-
import { Switch } from "@nurix/components/switch";
|
|
8
|
-
import { Badge } from "@nurix/components/badge";
|
|
9
|
-
import { Kbd } from "@nurix/components/kbd";
|
|
10
|
-
import { Skeleton } from "@nurix/components/skeleton";
|
|
11
|
-
import { EmptyState } from "@nurix/components/empty-state";
|
|
12
|
-
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@nurix/components/native-select";
|
|
13
|
-
import { cn } from "@nurix/components/utils";
|
|
14
|
-
|
|
15
|
-
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
16
|
-
|
|
17
|
-
export interface LogLine {
|
|
18
|
-
id: string; timestamp: string; level: LogLevel; source: string; message: string; status: number | null; latencyMs: number | null; detail: Record<string, string | number>;
|
|
19
|
-
}
|
|
20
|
-
export interface DegradedInfo { droppedLines: number; reason: string }
|
|
21
|
-
export interface LogStreamProps {
|
|
22
|
-
lines?: LogLine[]; isLoading?: boolean; error?: string | null; degraded?: DegradedInfo | null; initiallyFollowing?: boolean; onRetry?: () => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const RANK: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
26
|
-
const RANK_FLOOR: Record<string, number> = { all: 0, info: 1, warn: 2, error: 3 };
|
|
27
|
-
const LEVEL_META: Record<LogLevel, { label: string; className: string }> = {
|
|
28
|
-
error: { label: "ERR", className: "text-destructive" },
|
|
29
|
-
warn: { label: "WARN", className: "text-warning" },
|
|
30
|
-
info: { label: "INFO", className: "text-foreground" },
|
|
31
|
-
debug: { label: "DBG", className: "text-muted-foreground" },
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const LEVEL_OPTIONS: { value: string; label: string }[] = [
|
|
35
|
-
{ value: "all", label: "All levels" },
|
|
36
|
-
{ value: "info", label: "Info and up" },
|
|
37
|
-
{ value: "warn", label: "Warnings and up" },
|
|
38
|
-
{ value: "error", label: "Errors only" },
|
|
39
|
-
];
|
|
40
|
-
// Slice the stable "HH:MM:SS.mmm" clock out of an ISO timestamp (timezone-free, deterministic).
|
|
41
|
-
function formatClock(iso: string): string {
|
|
42
|
-
return iso.slice(11, 23);
|
|
43
|
-
}
|
|
44
|
-
// Test whether a line matches the free-text filter across message and source.
|
|
45
|
-
function matchesQuery(line: LogLine, needle: string): boolean {
|
|
46
|
-
if (!needle) return true;
|
|
47
|
-
const q = needle.toLowerCase();
|
|
48
|
-
return line.message.toLowerCase().includes(q) || line.source.toLowerCase().includes(q);
|
|
49
|
-
}
|
|
50
|
-
// Append-only edge-ingest log stream: filter, tail-follow, and inline-expand immutable events.
|
|
51
|
-
export function LogStream({ lines, isLoading = false, error = null, degraded = null, initiallyFollowing = true, onRetry }: LogStreamProps) {
|
|
52
|
-
const seed = lines ?? SEED_LINES;
|
|
53
|
-
const [query, setQuery] = React.useState("");
|
|
54
|
-
const [level, setLevel] = React.useState("all");
|
|
55
|
-
const [isFollowing, setIsFollowing] = React.useState(initiallyFollowing);
|
|
56
|
-
const [liveCount, setLiveCount] = React.useState(0);
|
|
57
|
-
const [activeIndex, setActiveIndex] = React.useState(0);
|
|
58
|
-
const [expandedId, setExpandedId] = React.useState<string | null>(null);
|
|
59
|
-
const rowRefs = React.useRef<(HTMLButtonElement | null)[]>([]);
|
|
60
|
-
const bottomRef = React.useRef<HTMLDivElement | null>(null);
|
|
61
|
-
const searchRef = React.useRef<HTMLInputElement | null>(null);
|
|
62
|
-
|
|
63
|
-
const allLines = [...seed, ...STREAM_LINES.slice(0, liveCount)];
|
|
64
|
-
const visible = allLines.filter((l) => RANK[l.level] >= RANK_FLOOR[level] && matchesQuery(l, query));
|
|
65
|
-
const hasFilter = query.trim() !== "" || level !== "all";
|
|
66
|
-
|
|
67
|
-
// Advance the deterministic tail one line at a time while following, until the pool drains.
|
|
68
|
-
React.useEffect(() => {
|
|
69
|
-
if (!isFollowing || liveCount >= STREAM_LINES.length) return;
|
|
70
|
-
const id = setInterval(() => setLiveCount((n) => Math.min(STREAM_LINES.length, n + 1)), 2000);
|
|
71
|
-
return () => clearInterval(id);
|
|
72
|
-
}, [isFollowing, liveCount]);
|
|
73
|
-
|
|
74
|
-
// Keep the newest line in view whenever the tail grows and follow is engaged.
|
|
75
|
-
React.useEffect(() => {
|
|
76
|
-
if (isFollowing) bottomRef.current?.scrollIntoView({ block: "end" });
|
|
77
|
-
}, [visible.length, isFollowing]);
|
|
78
|
-
|
|
79
|
-
// Roving-tabindex row travel: arrows and Home/End move focus without leaving the list.
|
|
80
|
-
function onListKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {
|
|
81
|
-
const last = visible.length - 1;
|
|
82
|
-
let next = activeIndex;
|
|
83
|
-
if (e.key === "ArrowDown") next = Math.min(last, activeIndex + 1);
|
|
84
|
-
else if (e.key === "ArrowUp") next = Math.max(0, activeIndex - 1);
|
|
85
|
-
else if (e.key === "Home") next = 0;
|
|
86
|
-
else if (e.key === "End") next = last;
|
|
87
|
-
else return;
|
|
88
|
-
e.preventDefault();
|
|
89
|
-
setActiveIndex(next);
|
|
90
|
-
rowRefs.current[next]?.focus();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Focus the filter input on "/" from anywhere outside a text field.
|
|
94
|
-
function onRootKeyDown(e: React.KeyboardEvent<HTMLElement>) {
|
|
95
|
-
if (e.key === "/" && !(e.target as HTMLElement).matches("input, textarea, [contenteditable]")) {
|
|
96
|
-
e.preventDefault();
|
|
97
|
-
searchRef.current?.focus();
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<section onKeyDown={onRootKeyDown} data-density-step="tighter" className="flex min-h-[100dvh] flex-col bg-background font-sans text-foreground">
|
|
103
|
-
<header className="flex flex-col gap-2 border-b border-border px-4 py-3">
|
|
104
|
-
<div className="flex items-center justify-between gap-3">
|
|
105
|
-
<div className="flex items-center gap-2">
|
|
106
|
-
<ScrollText aria-hidden className="size-4 text-muted-foreground" />
|
|
107
|
-
<h1 className="text-h2">Edge ingest log</h1>
|
|
108
|
-
<Badge variant="outline" className="gap-1.5">
|
|
109
|
-
<Circle aria-hidden className={cn("size-2 fill-current", isFollowing ? "text-success animate-pulse motion-reduce:animate-none" : "text-muted-foreground")} />{isFollowing ? "Live" : "Paused"}
|
|
110
|
-
</Badge>
|
|
111
|
-
</div>
|
|
112
|
-
<label className="flex items-center gap-2 text-caption text-muted-foreground">Follow tail
|
|
113
|
-
<Switch checked={isFollowing} onChange={setIsFollowing} aria-label="Follow the live tail" />
|
|
114
|
-
</label>
|
|
115
|
-
</div>
|
|
116
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
117
|
-
<div className="relative min-w-0 flex-1">
|
|
118
|
-
<Search aria-hidden className="pointer-events-none absolute left-2 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
|
119
|
-
<Input ref={searchRef} type="search" aria-label="Filter log lines" value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Filter by message or source" className="pl-8" />
|
|
120
|
-
</div>
|
|
121
|
-
<Select value={level} onValueChange={setLevel}>
|
|
122
|
-
<SelectTrigger aria-label="Minimum severity" className="w-44"><SelectValue /></SelectTrigger>
|
|
123
|
-
<SelectContent>{LEVEL_OPTIONS.map((o) => (<SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>))}</SelectContent>
|
|
124
|
-
</Select>
|
|
125
|
-
<span className="hidden items-center gap-1 text-caption text-muted-foreground sm:flex">
|
|
126
|
-
<Kbd size="sm">↑</Kbd><Kbd size="sm">↓</Kbd> move<Kbd size="sm">Enter</Kbd> expand
|
|
127
|
-
</span>
|
|
128
|
-
</div>
|
|
129
|
-
</header>
|
|
130
|
-
{degraded ? (
|
|
131
|
-
<div role="status" className="flex items-center gap-2 border-b border-border bg-muted px-4 py-1.5 text-caption">
|
|
132
|
-
<TriangleAlert aria-hidden className="size-4 shrink-0 text-warning" />
|
|
133
|
-
<span className="font-medium">Buffer truncated.</span>
|
|
134
|
-
<span className="tabular-nums text-muted-foreground">{degraded.droppedLines.toLocaleString()} lines dropped. {degraded.reason}</span>
|
|
135
|
-
</div>
|
|
136
|
-
) : null}
|
|
137
|
-
{error ? (
|
|
138
|
-
<div role="alert" className="flex min-h-0 flex-1 flex-col items-center justify-center gap-3 px-4 py-8 text-center">
|
|
139
|
-
<WifiOff aria-hidden className="size-8 text-destructive" />
|
|
140
|
-
<div className="space-y-1">
|
|
141
|
-
<p className="text-h3">Log stream disconnected</p>
|
|
142
|
-
<p className="max-w-md text-small text-muted-foreground">{error}</p>
|
|
143
|
-
</div>
|
|
144
|
-
<div role="status" className="flex items-center gap-2 text-caption text-muted-foreground">
|
|
145
|
-
<RotateCw aria-hidden className="size-4 animate-spin motion-reduce:animate-none" />Reconnecting to the collector…
|
|
146
|
-
</div>
|
|
147
|
-
<Button size="sm" onClick={onRetry}>Retry connection</Button>
|
|
148
|
-
</div>
|
|
149
|
-
) : isLoading ? (
|
|
150
|
-
<div aria-hidden className="min-h-0 flex-1 overflow-hidden font-mono">
|
|
151
|
-
{SKELETON_ROWS.map((w, i) => (
|
|
152
|
-
<div key={i} className="flex items-center gap-3 border-b border-border px-4 py-1">
|
|
153
|
-
<Skeleton className="size-3.5 shrink-0 rounded-full" /><Skeleton className="h-3 w-24 shrink-0" /><Skeleton className="h-3 w-12 shrink-0" />
|
|
154
|
-
<Skeleton className="h-3 w-36 shrink-0" /><Skeleton className={cn("h-3", w)} />
|
|
155
|
-
</div>
|
|
156
|
-
))}
|
|
157
|
-
</div>
|
|
158
|
-
) : (
|
|
159
|
-
<div className="flex items-center gap-3 border-b border-border px-4 py-1.5 text-caption font-medium uppercase tracking-wide text-muted-foreground">
|
|
160
|
-
<span className="w-[13ch] shrink-0 pl-6">Time</span><span className="w-12 shrink-0">Level</span><span className="w-36 shrink-0">Source</span>
|
|
161
|
-
<span className="min-w-0 flex-1">Message</span><span className="w-14 shrink-0 text-right">Status</span><span className="w-24 shrink-0 text-right">Latency</span>
|
|
162
|
-
</div>
|
|
163
|
-
)}
|
|
164
|
-
{!error && !isLoading ? (
|
|
165
|
-
<div role="log" aria-live="polite" aria-relevant="additions" aria-label="Edge ingest log stream, newest last" onKeyDown={onListKeyDown} className="min-h-0 flex-1 overflow-auto font-mono text-small">
|
|
166
|
-
{visible.length === 0 ? (
|
|
167
|
-
<div className="flex h-full items-center justify-center px-4 py-8">
|
|
168
|
-
{hasFilter ? (
|
|
169
|
-
<EmptyState icon={<ListFilter aria-hidden className="size-8 text-muted-foreground" />} title="No lines match these filters" description="Events are still arriving, but none match the current search and severity. Loosen the filter to see the tail again." action={<Button size="sm" variant="secondary" onClick={() => { setQuery(""); setLevel("all"); }}>Clear filters</Button>} />
|
|
170
|
-
) : (
|
|
171
|
-
<EmptyState icon={<Inbox aria-hidden className="size-8 text-muted-foreground" />} title="No events received yet" description="This stream is connected and following the edge-ingest collector. New lines appear here as requests are processed." />
|
|
172
|
-
)}
|
|
173
|
-
</div>
|
|
174
|
-
) : (
|
|
175
|
-
<>
|
|
176
|
-
{visible.map((line, i) => {
|
|
177
|
-
const meta = LEVEL_META[line.level];
|
|
178
|
-
const isOpen = expandedId === line.id;
|
|
179
|
-
return (
|
|
180
|
-
<div key={line.id} className="border-b border-border">
|
|
181
|
-
<button ref={(el) => { rowRefs.current[i] = el; }} tabIndex={i === activeIndex ? 0 : -1} aria-expanded={isOpen} onClick={() => { setActiveIndex(i); setExpandedId((cur) => (cur === line.id ? null : line.id)); }} className="motion-lift flex w-full items-center gap-3 px-4 py-1 text-left hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring">
|
|
182
|
-
{isOpen ? <ChevronDown aria-hidden className="size-3.5 shrink-0 text-muted-foreground" /> : <ChevronRight aria-hidden className="size-3.5 shrink-0 text-muted-foreground" />}
|
|
183
|
-
<span className="w-[13ch] shrink-0 tabular-nums text-muted-foreground">{formatClock(line.timestamp)}</span>
|
|
184
|
-
<span className={cn("w-12 shrink-0 font-medium", meta.className)}>{meta.label}</span>
|
|
185
|
-
<span className="w-36 shrink-0 truncate text-muted-foreground">{line.source}</span>
|
|
186
|
-
<span className="min-w-0 flex-1 truncate">{line.message}</span>
|
|
187
|
-
<span className="w-14 shrink-0 text-right tabular-nums text-muted-foreground">{line.status ?? "–"}</span>
|
|
188
|
-
<span className="w-24 shrink-0 text-right tabular-nums text-muted-foreground">{line.latencyMs == null ? "–" : `${line.latencyMs} ms`}</span>
|
|
189
|
-
</button>
|
|
190
|
-
{isOpen ? (
|
|
191
|
-
<dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 border-t border-border bg-muted px-4 py-2 pl-11 text-muted-foreground">
|
|
192
|
-
{Object.entries(line.detail).map(([k, v]) => (
|
|
193
|
-
<React.Fragment key={k}><dt>{k}</dt><dd className="truncate tabular-nums text-foreground">{String(v)}</dd></React.Fragment>
|
|
194
|
-
))}
|
|
195
|
-
</dl>
|
|
196
|
-
) : null}
|
|
197
|
-
</div>
|
|
198
|
-
);
|
|
199
|
-
})}
|
|
200
|
-
<div ref={bottomRef} aria-hidden />
|
|
201
|
-
</>
|
|
202
|
-
)}
|
|
203
|
-
</div>
|
|
204
|
-
) : null}
|
|
205
|
-
<footer className="flex items-center justify-between gap-3 border-t border-border px-4 py-1.5 text-caption text-muted-foreground">
|
|
206
|
-
<span className="tabular-nums">Showing {visible.length} of {allLines.length} lines</span>
|
|
207
|
-
<span className="hidden items-center gap-1 sm:flex"><Kbd size="sm">/</Kbd> to filter</span>
|
|
208
|
-
</footer>
|
|
209
|
-
</section>
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// exemplar fixture data — seeded, domain-shaped
|
|
214
|
-
const SEED_LINES: LogLine[] = [
|
|
215
|
-
{ id: "l-01", timestamp: "2026-07-04T09:12:03.109Z", level: "debug", source: "edge-ingest.tls", message: "TLS handshake completed for client 10.2.4.19", status: null, latencyMs: 4, detail: { cipher: "TLS_AES_128_GCM", client_ip: "10.2.4.19", alpn: "h2" } },
|
|
216
|
-
{ id: "l-02", timestamp: "2026-07-04T09:12:03.842Z", level: "info", source: "edge-ingest.router", message: "POST /v1/events accepted, 3 records queued", status: 202, latencyMs: 34, detail: { upstream: "queue.primary", partition: 4, trace_id: "trc_a19f3c", bytes_in: 2048 } },
|
|
217
|
-
{ id: "l-03", timestamp: "2026-07-04T09:12:04.210Z", level: "info", source: "edge-ingest.router", message: "GET /v1/streams/live upgraded to websocket", status: 101, latencyMs: 12, detail: { upstream: "gateway.ws", trace_id: "trc_b2e0d1", conn_id: "ws_7741" } },
|
|
218
|
-
{ id: "l-04", timestamp: "2026-07-04T09:12:05.556Z", level: "warn", source: "edge-ingest.ratelimit", message: "Client tenant_4821 throttled, 429 returned", status: 429, latencyMs: 8, detail: { tenant: "tenant_4821", limit: "600/min", retry_after_s: 30, trace_id: "trc_c4d1a8" } },
|
|
219
|
-
{ id: "l-05", timestamp: "2026-07-04T09:12:06.001Z", level: "debug", source: "edge-ingest.cache", message: "Config snapshot refreshed from control-plane", status: null, latencyMs: 21, detail: { version: 1183, keys: 47, source_region: "eu-central" } },
|
|
220
|
-
{ id: "l-06", timestamp: "2026-07-04T09:12:06.733Z", level: "error", source: "edge-ingest.writer", message: "Upstream queue.primary timed out after 5000 ms", status: 504, latencyMs: 5003, detail: { upstream: "queue.primary", trace_id: "trc_d9a2f0", attempt: 2, error: "context deadline exceeded" } },
|
|
221
|
-
{ id: "l-07", timestamp: "2026-07-04T09:12:07.418Z", level: "info", source: "edge-ingest.writer", message: "Retry succeeded on queue.secondary", status: 202, latencyMs: 61, detail: { upstream: "queue.secondary", trace_id: "trc_d9a2f0", attempt: 3, bytes_in: 2048 } },
|
|
222
|
-
{ id: "l-08", timestamp: "2026-07-04T09:12:08.902Z", level: "warn", source: "edge-ingest.schema", message: "Event field ts coerced from string to int64", status: 202, latencyMs: 39, detail: { trace_id: "trc_e5f712", field: "ts", records: 3 } },
|
|
223
|
-
{ id: "l-09", timestamp: "2026-07-04T09:12:09.377Z", level: "info", source: "edge-ingest.router", message: "POST /v1/events accepted, 12 records queued", status: 202, latencyMs: 44, detail: { upstream: "queue.primary", partition: 1, trace_id: "trc_f00312", bytes_in: 8192 } },
|
|
224
|
-
{ id: "l-10", timestamp: "2026-07-04T09:12:09.481Z", level: "debug", source: "edge-ingest.dns", message: "Resolved queue.primary to 10.8.0.14 from cache", status: null, latencyMs: 0, detail: { host: "queue.primary", ttl_s: 42, cached: "true" } },
|
|
225
|
-
{ id: "l-11", timestamp: "2026-07-04T09:12:09.502Z", level: "warn", source: "edge-ingest.router", message: "POST /v1/events rejected: payload 12.4 MB exceeds the 10 MB per-batch ceiling for tenant_2210", status: 413, latencyMs: 18, detail: { tenant: "tenant_2210", bytes_in: 13002744, limit_bytes: 10485760, trace_id: "trc_04c1aa" } },
|
|
226
|
-
{ id: "l-12", timestamp: "2026-07-04T09:12:09.594Z", level: "info", source: "edge-ingest.compress", message: "Batch recompressed zstd level 3, ratio 6.2:1", status: null, latencyMs: 11, detail: { codec: "zstd", ratio: "6.2:1", bytes_out: 1322 } },
|
|
227
|
-
{ id: "l-13", timestamp: "2026-07-04T09:12:09.688Z", level: "error", source: "edge-ingest.router", message: "POST /v1/events malformed JSON at offset 1024, batch dropped", status: 400, latencyMs: 6, detail: { tenant: "tenant_8804", offset: 1024, trace_id: "trc_15d2bb", error: "unexpected end of input" } },
|
|
228
|
-
{ id: "l-14", timestamp: "2026-07-04T09:12:09.771Z", level: "info", source: "edge-ingest.writer", message: "Batch flushed to queue.primary, 47 records", status: 202, latencyMs: 52, detail: { upstream: "queue.primary", flushed: 47, trace_id: "trc_26e3cc" } },
|
|
229
|
-
{ id: "l-15", timestamp: "2026-07-04T09:12:09.845Z", level: "debug", source: "edge-ingest.cache", message: "Hot-set hit ratio 0.94 over the last 10s window", status: null, latencyMs: null, detail: { hits: 8812, misses: 561, window_s: 10 } },
|
|
230
|
-
{ id: "l-16", timestamp: "2026-07-04T09:12:09.912Z", level: "info", source: "edge-ingest.router", message: "GET /v1/streams/live upgraded to websocket", status: 101, latencyMs: 9, detail: { upstream: "gateway.ws", conn_id: "ws_7742", trace_id: "trc_37f4dd" } },
|
|
231
|
-
{ id: "l-17", timestamp: "2026-07-04T09:12:10.033Z", level: "warn", source: "edge-ingest.schema", message: "Field amount_cents received as float for 9 records, coerced to int64 with rounding", status: 202, latencyMs: 41, detail: { field: "amount_cents", records: 9, trace_id: "trc_48a5ee" } },
|
|
232
|
-
{ id: "l-18", timestamp: "2026-07-04T09:12:10.127Z", level: "info", source: "edge-ingest.router", message: "POST /v1/events accepted, 3 records queued", status: 202, latencyMs: 28, detail: { upstream: "queue.primary", partition: 2, trace_id: "trc_59b6ff", bytes_in: 1536 } },
|
|
233
|
-
{ id: "l-19", timestamp: "2026-07-04T09:12:10.204Z", level: "error", source: "edge-ingest.writer", message: "queue.secondary returned 503, circuit breaker half-open after 2 consecutive failures", status: 503, latencyMs: 2891, detail: { upstream: "queue.secondary", attempt: 2, breaker: "half-open", trace_id: "trc_6ac700" } },
|
|
234
|
-
{ id: "l-20", timestamp: "2026-07-04T09:12:10.311Z", level: "debug", source: "edge-ingest.tls", message: "TLS handshake completed for client 10.2.7.44", status: null, latencyMs: 5, detail: { cipher: "TLS_AES_256_GCM", client_ip: "10.2.7.44", alpn: "h2" } },
|
|
235
|
-
{ id: "l-21", timestamp: "2026-07-04T09:12:10.398Z", level: "info", source: "edge-ingest.auth", message: "Token refreshed for tenant_4821, scope events:write", status: 200, latencyMs: 14, detail: { tenant: "tenant_4821", scope: "events:write", expires_in_s: 3600 } },
|
|
236
|
-
{ id: "l-22", timestamp: "2026-07-04T09:12:10.476Z", level: "warn", source: "edge-ingest.ratelimit", message: "Client tenant_9917 throttled, 429 returned", status: 429, latencyMs: 7, detail: { tenant: "tenant_9917", limit: "300/min", retry_after_s: 60, trace_id: "trc_7bd811" } },
|
|
237
|
-
{ id: "l-23", timestamp: "2026-07-04T09:12:10.581Z", level: "info", source: "edge-ingest.router", message: "GET /v1/health checked by readiness probe", status: 200, latencyMs: 2, detail: { probe: "readiness", trace_id: "trc_8ce922" } },
|
|
238
|
-
{ id: "l-24", timestamp: "2026-07-04T09:12:10.672Z", level: "info", source: "edge-ingest.writer", message: "Batch flushed to queue.primary, 21 records", status: 202, latencyMs: 49, detail: { upstream: "queue.primary", flushed: 21, trace_id: "trc_9df033" } },
|
|
239
|
-
];
|
|
240
|
-
|
|
241
|
-
const STREAM_LINES: LogLine[] = [
|
|
242
|
-
{ id: "s-01", timestamp: "2026-07-04T09:12:10.845Z", level: "info", source: "edge-ingest.router", message: "GET /v1/health checked by readiness probe", status: 200, latencyMs: 3, detail: { probe: "readiness", trace_id: "trc_1a4409" } },
|
|
243
|
-
{ id: "s-02", timestamp: "2026-07-04T09:12:12.110Z", level: "debug", source: "edge-ingest.cache", message: "Evicted 128 stale entries from hot set", status: null, latencyMs: 2, detail: { evicted: 128, hot_set: 4096 } },
|
|
244
|
-
{ id: "s-03", timestamp: "2026-07-04T09:12:13.492Z", level: "error", source: "edge-ingest.auth", message: "Token signature rejected for tenant_5590", status: 401, latencyMs: 6, detail: { tenant: "tenant_5590", reason: "kid not found", trace_id: "trc_2b7133" } },
|
|
245
|
-
{ id: "s-04", timestamp: "2026-07-04T09:12:14.938Z", level: "warn", source: "edge-ingest.ratelimit", message: "Client tenant_4821 at 92% of quota", status: 200, latencyMs: 7, detail: { tenant: "tenant_4821", used: "552/600", trace_id: "trc_3c0955" } },
|
|
246
|
-
{ id: "s-05", timestamp: "2026-07-04T09:12:16.201Z", level: "info", source: "edge-ingest.writer", message: "Batch flushed to queue.primary, 12 records", status: 202, latencyMs: 58, detail: { upstream: "queue.primary", trace_id: "trc_f00312", flushed: 12 } },
|
|
247
|
-
];
|
|
248
|
-
|
|
249
|
-
const SKELETON_ROWS = ["w-2/3", "w-1/2", "w-3/4", "w-2/5", "w-3/5", "w-1/2", "w-2/3", "w-1/3", "w-3/5", "w-1/2"];
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# Fixtures — data as design material
|
|
2
|
-
|
|
3
|
-
The rows an operator surface renders are not scaffolding for the "real" design — they **are** the design input. Density, truncation, overflow, and every lifecycle state are properties of *the data under them*: a queue is dense or sparse because of how many rows it holds and how long their strings run; a cell truncates or wraps because of what it contains; an empty state exists only when the query returns nothing. Design them against three short rows named "Item 1" and you have designed nothing that production will look like.
|
|
4
|
-
|
|
5
|
-
So fixtures come **first** — generated before any surface is composed (the create `fixtures` phase, [create.md](../../references/create.md)) — and they are realistic, seeded, and volume-bearing. **Rows exist to count.** The `dep`/`spc` density thresholds and the `sta` state-presence rules are only *bindable* against real volumes, long strings, and unhappy values; without them the proxies (rows per viewport, cells per screen) have nothing to measure.
|
|
6
|
-
|
|
7
|
-
**Loads** at the create `fixtures` phase, and whenever improve/introspect diagnoses density or state coverage on an existing UI. Reads the entity and `temporal` facts from `docs/design/structure.md`. Pairs with [lifecycle.md](../../references/lifecycle.md) — this file produces the data those states are filled with.
|
|
8
|
-
|
|
9
|
-
## What "realistic" means
|
|
10
|
-
|
|
11
|
-
A fixture requirement is not "some data" — it is data shaped like production, exercising the conditions the hard dimensions live in. Every entity's fixtures satisfy all of these:
|
|
12
|
-
|
|
13
|
-
- **Domain-shaped.** Names, values, and identifiers that could be a real row — real assignee names, real-looking subjects, plausible timestamps and statuses. Never `Item 1` / `Row 2`, never lorem ipsum, never `John Doe` / `Acme` / `Nexus`.
|
|
14
|
-
- **Seeded and deterministic.** Same seed → same rows, every run. Randomness runs through falso's seed API (`seed(...)` before generation) or a local PRNG — never `Math.random()`. A fixture that shifts between runs is not a fixture.
|
|
15
|
-
- **Volume that exercises density.** Queues and tables carry realistic depth — hundreds of rows (a 500-row queue), not a handful. Density is unmeasurable on five rows.
|
|
16
|
-
- **Long strings that exercise truncation.** Some subjects, names, and paths run long enough to force the truncation and wrapping decisions — mixed with short ones, so both the tight and the overflowing case are on screen at once.
|
|
17
|
-
- **Unhappy values.** `null` and missing fields, zero, negative numbers, huge numbers, empty collections, overdue and far-future dates, statuses at every lifecycle stage. The happy row is the rare row.
|
|
18
|
-
- **Degraded payloads.** Partial objects and failed enrichments — a row whose join didn't resolve, a record whose secondary fetch returned nothing — so the `partial/degraded` state has real data to render.
|
|
19
|
-
- **Explicit empty and error variants per surface.** Every surface's declared states ([lifecycle.md](../../references/lifecycle.md)) get their own fixture: an empty variant (`[]`) and a degraded variant. Error is the no-data case the state-shell owns, but the pack still declares the state so it is exercised.
|
|
20
|
-
|
|
21
|
-
## Module shape
|
|
22
|
-
|
|
23
|
-
One module per entity, at `src/fixtures/<entity>.ts`, exporting **typed arrays** plus the **seed constant** it was generated under. Per-surface state variants are named exports co-located in the entity's module, keyed to the surface states they feed (`emptyTickets`, `degradedTickets`). Surfaces import these arrays; the model **never** freehands data inline in a surface file.
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
// src/fixtures/tickets.ts — seeded ticket-queue fixtures (generated; do not hand-edit rows)
|
|
27
|
-
import { seed, randUuid, randFullName, randCompanyName, randRecentDate, randPastDate } from "@ngneat/falso";
|
|
28
|
-
import type { Ticket } from "../types/ticket";
|
|
29
|
-
|
|
30
|
-
export const TICKETS_SEED = 4173;
|
|
31
|
-
seed(String(TICKETS_SEED));
|
|
32
|
-
|
|
33
|
-
const STATUSES = ["new", "open", "pending", "escalated", "resolved", "closed"] as const;
|
|
34
|
-
const LONG_SUBJECT =
|
|
35
|
-
"Checkout returns 500 on multi-currency carts after the 3.4 billing migration when tax is estimated";
|
|
36
|
-
|
|
37
|
-
// One row shaped like production — real names, real timestamps, unhappy values seeded in by index.
|
|
38
|
-
function buildTicket(_: unknown, i: number): Ticket {
|
|
39
|
-
const overdue = i % 7 === 0;
|
|
40
|
-
const enrichmentFailed = i % 11 === 0;
|
|
41
|
-
return {
|
|
42
|
-
id: randUuid(),
|
|
43
|
-
subject: i % 5 === 0 ? LONG_SUBJECT : `Payment retry failing for ${randCompanyName()}`,
|
|
44
|
-
status: STATUSES[i % STATUSES.length],
|
|
45
|
-
assignee: i % 9 === 0 ? null : randFullName(),
|
|
46
|
-
account: enrichmentFailed ? null : randCompanyName(),
|
|
47
|
-
slaMinutesRemaining: overdue ? -Math.floor(i * 3.5) : 240 - (i % 240),
|
|
48
|
-
replies: i % 13 === 0 ? 0 : i % 47,
|
|
49
|
-
dueAt: overdue ? randPastDate() : randRecentDate(),
|
|
50
|
-
createdAt: randPastDate(),
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// 500 rows exercise queue density; statuses distributed across the full lifecycle.
|
|
55
|
-
export const tickets: Ticket[] = Array.from({ length: 500 }, buildTicket);
|
|
56
|
-
|
|
57
|
-
// The same surface, empty: a first-time or filtered-to-nothing queue.
|
|
58
|
-
export const emptyTickets: Ticket[] = [];
|
|
59
|
-
|
|
60
|
-
// The same surface, degraded: enrichment failed — assignee and account come back null.
|
|
61
|
-
export const degradedTickets: Ticket[] = tickets
|
|
62
|
-
.slice(0, 12)
|
|
63
|
-
.map((t) => ({ ...t, assignee: null, account: null, slaMinutesRemaining: 0 }));
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
The `Ticket` type lives in `src/types/`; fixtures import it, never redefine it. The seed constant is exported so the data is reproducible and traceable — regeneration under the same seed yields byte-identical rows.
|
|
67
|
-
|
|
68
|
-
## The generator contract
|
|
69
|
-
|
|
70
|
-
`generate-fixtures.mjs <structure.md> --seed <n> --out <dir>` derives the entity shapes from the structure map and emits these modules. It never guesses a shape — the map is the source:
|
|
71
|
-
|
|
72
|
-
- **From each entity's `attributes`** it derives the field set and picks a falso generator per field by name and type (a `name` field → `randFullName`, a `*_at` date → `randRecentDate`, an amount → a seeded number in a plausible range).
|
|
73
|
-
- **From each entity's `lifecycle`** it distributes rows across the status values, so every state in the model appears in the data.
|
|
74
|
-
- **From `relationships`** it wires cross-entity references to real ids from the related module, so joins resolve to actual rows (and degraded variants are the ones where they deliberately don't).
|
|
75
|
-
- **From each surface's declared `states`** ([surface-catalog.md](surface-catalog.md), `structure.md`) it emits the empty and degraded variants that surface needs.
|
|
76
|
-
|
|
77
|
-
The run's `--seed <n>` is the base; each module records the effective seed it generated under (`<ENTITY>_SEED`) so a single module reproduces independently and the whole run reproduces from one number ([create.md](../../references/create.md): "a fixed seed makes every run reproducible"). Volumes, long-string injection, unhappy-value distribution, and degraded payloads are the generator's job, not the model's — **the model wires surfaces to the emitted fixtures; it never authors rows.**
|
|
78
|
-
|
|
79
|
-
## Anti-patterns
|
|
80
|
-
|
|
81
|
-
Fixtures are mock data, so the anti-fabrication discipline applies to them in full — a mock row must read as *domain-real*, never as *fabricated-real*:
|
|
82
|
-
|
|
83
|
-
- **Lorem ipsum, `Item 1`, placeholder slop.** Domain-shaped values only.
|
|
84
|
-
- **`John Doe` / `Acme` / `Nexus` and invented vanity metrics.** No fabricated hero statistics (`99.98% UPTIME SLA`, `10,000+ customers`, fake round numbers) dressed as production achievements. Operator fixtures carry operational values — queue depths, timestamps, statuses — not marketing numbers.
|
|
85
|
-
- **The mock-marker convention on display numbers.** A fixture-supplied metric that a viewer could mistake for an authoritative production statistic is marked at the render site per the catalog convention — fake-precise mock numbers carry `<!-- mock -->`, unknowable values use a `[metric]` placeholder — so a fixture number is never read as ground truth.
|
|
86
|
-
- **Unseeded randomness.** `Math.random()`, `Date.now()`, or any non-reproducible source. Seed everything.
|
|
87
|
-
- **Fixtures authored after surfaces.** Data comes first; a surface composed before its data exists is designed against nothing. The `fixtures` phase gate fails if a surface file was written before the fixtures ([create.md](../../references/create.md)).
|
|
88
|
-
- **Happy-path-only rows.** No volume, no long strings, no unhappy or degraded values, no empty/error variants — the states and density that make an operator UI hard go unexercised, and their thresholds unbindable.
|