{
  "os-shell-patterns": [
    {
      "id": "os-shell-z-index-literal",
      "title": "OS shell z-index literal",
      "category": "convention",
      "severity": "high",
      "pattern": "z-index\\s*:\\s*[0-9]+",
      "description": "OS shell stack order must come from store order, not CSS z-index literals.",
      "resolution": "Delete the literal z-index and derive stack order from the shell store.",
      "canary": "z-index: 7;"
    },
    {
      "id": "os-shell-second-backdrop-filter",
      "title": "Second OS shell backdrop-filter recipe",
      "category": "convention",
      "severity": "high",
      "pattern": "backdrop-filter\\s*:\\s*(?!var\\(--shell-blur\\))",
      "description": "OS shell chrome uses one blur recipe only.",
      "resolution": "Use var(--shell-blur) instead of introducing a second backdrop-filter recipe.",
      "canary": "backdrop-filter: blur(22px);"
    },
    {
      "id": "os-shell-motion-literal",
      "title": "OS shell motion literal",
      "category": "convention",
      "severity": "high",
      "pattern": "(transition|animation)(-duration|-timing-function)?\\s*:\\s*[^;]*(ms|cubic-bezier\\(|\\bease\\b|\\blinear\\b)",
      "description": "OS shell motion must come from tokens, not inline literals.",
      "resolution": "Replace motion literals with shell motion tokens.",
      "canary": "transition: transform 180ms ease;"
    },
    {
      "id": "os-shell-drag-transition",
      "title": "Transition inside pointer-drag handler",
      "category": "convention",
      "severity": "high",
      "pattern": "onPointerMove[\\s\\S]{0,220}transition\\s*:",
      "description": "Pointer-drag handlers must not toggle transition styles.",
      "resolution": "Move transition ownership to settled states, never the live drag path.",
      "canary": "onPointerMove={() => { const style = 'transition: transform 120ms ease'; return style; }}"
    },
    {
      "id": "os-shell-missing-data-fx",
      "title": "Shell effect selector without data-fx",
      "category": "convention",
      "severity": "high",
      "pattern": "className=\\\"shell-effect\\\"(?![^>]*data-fx=)",
      "description": "Every shell effect surface must expose a data-fx hook for regression tests.",
      "resolution": "Add a stable data-fx attribute to the animated shell element.",
      "canary": "className=\"shell-effect\""
    },
    {
      "id": "os-mobile-scroll-physics",
      "title": "Mobile shell scroll physics reimplementation",
      "category": "convention",
      "severity": "high",
      "paths": ["apps/zync-app/src/os/mobile/**"],
      "pattern": "scroll(Top|Left)\\s*[+\\-*/]?=|scrollBy\\s*\\(|scrollTo\\s*\\(",
      "description": "Mobile shell gestures must use the shared gesture primitives rather than reimplementing scroll physics in JavaScript.",
      "resolution": "Use the shell gesture primitive and CSS overscroll behavior.",
      "canary": "element.scrollTop += 12"
    },
    {
      "id": "os-mobile-gesture-button-twin",
      "title": "Mobile gesture without button twin",
      "category": "convention",
      "severity": "high",
      "paths": ["apps/zync-app/src/os/mobile/**"],
      "pattern": "data-gesture=\\\"(?![^\\\"]*data-gesture-button=)",
      "description": "Every mobile gesture needs a visible button twin for keyboard and assistive-technology access.",
      "resolution": "Add a matching data-gesture-button to the visible button twin.",
      "canary": "data-gesture=\"shade-pull\""
    }
  ]
}
