[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"playground-typescript-satisfies-operator":32},[4,20],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Build. Learn. Ship.","Practical courses, engineering-grade articles and open-source tools for people who ship.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Structured paths, listening drills and spaced repetition that actually sticks.","amber-400","⌘",[13,15,29],"es",{"courses":17,"paths":17,"articles":18,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":17},2,{"slug":33,"title":34,"summary":35,"html":36,"css":37,"js":38,"config":39,"doc":41},"typescript-satisfies-operator","satisfies — validate without widening",null,"\u003Cdiv class=\"wrap\">\n    \u003Ch1>🎮 \u003Ccode>satisfies\u003C\u002Fcode>: validate without widening\u003C\u002Fh1>\n    \u003Cp class=\"sub\">\n      One route config, three ways to declare it. Flip between them and watch what TypeScript\n      \u003Cem>keeps\u003C\u002Fem> vs \u003Cem>throws away\u003C\u002Fem>. (Types are checked at compile time — this simulates it.)\n    \u003C\u002Fp>\n\n    \u003C!-- ============ PANEL 1: the declaration (primary control, above the fold) ============ -->\n    \u003Cdiv class=\"panel\">\n      \u003Ch2>1 · How is it declared?\u003C\u002Fh2>\n      \u003Cdiv class=\"seg\" role=\"group\" aria-label=\"Declaration style\">\n        \u003Cbutton data-mode=\"bare\"      aria-pressed=\"false\">bare\u003Cbr>\u003Cspan class=\"mono\">= { … }\u003C\u002Fspan>\u003C\u002Fbutton>\n        \u003Cbutton data-mode=\"annot\"     aria-pressed=\"true\">annotation\u003Cbr>\u003Cspan class=\"mono\">: Record&lt;…&gt;\u003C\u002Fspan>\u003C\u002Fbutton>\n        \u003Cbutton data-mode=\"satisfies\" aria-pressed=\"false\">satisfies\u003Cbr>\u003Cspan class=\"mono\">satisfies …\u003C\u002Fspan>\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n\n      \u003Cpre aria-live=\"polite\">\u003Ccode id=\"code\">\u003C\u002Fcode>\u003C\u002Fpre>\n\n      \u003Cdiv class=\"row\">\n        \u003Cbutton class=\"act\" id=\"breakBtn\" data-on=\"false\" aria-pressed=\"false\">💥 Break it (typo a path)\u003C\u002Fbutton>\n        \u003Cdiv class=\"spacer\">\u003C\u002Fdiv>\n        \u003Cbutton class=\"act\" id=\"resetBtn\">↺ Reset\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003C!-- ============ PANEL 2: what you get ============ -->\n    \u003Cdiv class=\"panel\">\n      \u003Ch2>2 · What TypeScript gives you back\u003C\u002Fh2>\n      \u003Cdiv class=\"res\">\n\n        \u003C!-- resolved value type -->\n        \u003Cdiv class=\"card\">\n          \u003Cdiv class=\"hd\">Resolved type — click a property to inspect\u003C\u002Fdiv>\n          \u003Cdiv class=\"pills\" role=\"group\" aria-label=\"Property to inspect\">\n            \u003Cbutton class=\"pill\" data-prop=\"home\"     aria-pressed=\"true\">NAV.home\u003C\u002Fbutton>\n            \u003Cbutton class=\"pill\" data-prop=\"about\"    aria-pressed=\"false\">NAV.about\u003C\u002Fbutton>\n            \u003Cbutton class=\"pill\" data-prop=\"settings\" aria-pressed=\"false\">NAV.settings\u003C\u002Fbutton>\n          \u003C\u002Fdiv>\n          \u003Cdiv class=\"typeline\" id=\"valType\">\u003C\u002Fdiv>\n          \u003Cdiv class=\"cap\" id=\"valCap\">\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n\n        \u003C!-- keys \u002F autocomplete -->\n        \u003Cdiv class=\"card\">\n          \u003Cdiv class=\"hd\">Keys &amp; autocomplete \u003Cspan id=\"keysType\" class=\"mono\">\u003C\u002Fspan>\u003C\u002Fdiv>\n          \u003Cdiv class=\"ac\">\n            \u003Cspan class=\"prefix\">NAV.\u003C\u002Fspan>\u003Cspan id=\"acLabel\">\u003C\u002Fspan>\n            \u003Cdiv class=\"menu\" id=\"acMenu\">\u003C\u002Fdiv>\n          \u003C\u002Fdiv>\n          \u003Cdiv class=\"cap\" id=\"keysCap\">\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n\n        \u003C!-- validation -->\n        \u003Cdiv class=\"card\">\n          \u003Cdiv class=\"hd\">Validation\u003C\u002Fdiv>\n          \u003Cdiv class=\"valline\" id=\"valid\">\u003C\u002Fdiv>\n          \u003Cdiv class=\"cap\" id=\"validCap\">\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003C!-- ============ status \u002F plain-English takeaway ============ -->\n    \u003Cdiv class=\"status\" id=\"status\">\u003C\u002Fdiv>\n  \u003C\u002Fdiv>",":root {\n    --bg: #0d1117; --panel: #161b22; --ink: #e8ebf2; --muted: #9aa4b2;\n    --accent: #7c9cff; --good: #3fb950; --bad: #f0883e; --err: #f85149;\n    --type: #79c0ff; --line: #232a35;\n    font-family: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n  }\n  * { box-sizing: border-box; }\n  body { margin: 0; background: var(--bg); color: var(--ink); min-height: 100vh;\n    display: flex; align-items: flex-start; justify-content: center; padding: 18px; }\n  .wrap { width: 100%; max-width: 720px; }\n  h1 { font-size: 1.2rem; margin: 0 0 3px; letter-spacing: -0.01em; }\n  .sub { color: var(--muted); font-size: .9rem; margin: 0 0 14px; line-height: 1.45; }\n  code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }\n  .sub code { color: var(--accent); background: #ffffff10; padding: 1px 5px; border-radius: 5px; }\n\n  .panel { background: var(--panel); border: 1px solid var(--line);\n    border-radius: 14px; padding: 16px; margin-bottom: 14px; }\n  .panel > h2 { font-size: .8rem; text-transform: uppercase; letter-spacing: .06em;\n    color: var(--muted); margin: 0 0 12px; font-weight: 700; }\n\n  \u002F* ---- Segmented control (the primary control) ---- *\u002F\n  .seg { display: inline-flex; background: #0b0e14; border: 1px solid var(--line);\n    border-radius: 11px; padding: 4px; gap: 4px; width: 100%; }\n  .seg button { flex: 1; font: inherit; font-weight: 600; font-size: .82rem;\n    color: var(--muted); background: transparent; border: 0; border-radius: 8px;\n    padding: 9px 8px; cursor: pointer; white-space: nowrap; overflow: hidden;\n    text-overflow: ellipsis; transition: background .12s, color .12s; }\n  .seg button .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n    font-size: .78rem; }\n  .seg button[aria-pressed=\"true\"] { background: var(--accent); color: #0b0d10; }\n  .seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n\n  .row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-top: 12px; }\n  button.act { font: inherit; font-weight: 600; font-size: .84rem; border-radius: 9px;\n    padding: 8px 14px; cursor: pointer; border: 1px solid var(--line);\n    background: transparent; color: var(--ink); }\n  button.act:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n  button#breakBtn[data-on=\"true\"] { background: #f0883e1a; border-color: #f0883e66; color: var(--bad); }\n  .spacer { flex: 1; }\n\n  \u002F* ---- Code block ---- *\u002F\n  pre { margin: 12px 0 0; overflow-x: auto; background: #0b0e14; border: 1px solid var(--line);\n    border-radius: 10px; padding: 13px 14px; font-size: .82rem; line-height: 1.65;\n    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }\n  .kw { color: var(--accent); } .ty { color: var(--type); } .str { color: var(--good); }\n  .cm { color: #6b7688; } .pr { color: var(--ink); }\n  .bad { color: var(--err); text-decoration: wavy underline var(--err); text-underline-offset: 3px; }\n\n  \u002F* ---- Results ---- *\u002F\n  .res { display: grid; gap: 10px; }\n  .card { border: 1px solid var(--line); border-radius: 11px; padding: 12px 13px; background: #0b0e14; }\n  .card .hd { font-size: .72rem; text-transform: uppercase; letter-spacing: .05em;\n    color: var(--muted); font-weight: 700; margin-bottom: 8px; display: flex; align-items: center; gap: 8px; }\n  .cap { font-size: .78rem; color: var(--muted); margin-top: 8px; line-height: 1.4; }\n  .cap .lit { color: var(--accent); }\n\n  \u002F* inspect pills *\u002F\n  .pills { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 10px; }\n  .pill { font: inherit; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n    font-size: .8rem; color: var(--ink); background: transparent; border: 1px solid var(--line);\n    border-radius: 8px; padding: 6px 10px; cursor: pointer; }\n  .pill[aria-pressed=\"true\"] { border-color: var(--accent); background: #7c9cff1a; color: var(--accent); }\n  .pill:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n\n  .typeline { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n    font-size: .95rem; line-height: 1.5; word-break: break-word; }\n  .typeline .arrow { color: var(--muted); }\n  .lit { color: var(--accent); font-weight: 700; }         \u002F* precise \u002F kept *\u002F\n  .wide { color: var(--muted); }                            \u002F* widened \u002F lost precision *\u002F\n  .wide.tyname { color: var(--type); opacity: .85; }\n  .badge { display: inline-block; font-size: .66rem; font-weight: 700; letter-spacing: .04em;\n    text-transform: uppercase; padding: 2px 7px; border-radius: 999px; margin-left: 2px; vertical-align: middle; }\n  .badge.keep { color: var(--good); background: #3fb9501a; }\n  .badge.lost { color: var(--bad); background: #f0883e1a; }\n\n  \u002F* autocomplete *\u002F\n  .ac { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .85rem; }\n  .ac .prefix { color: var(--muted); }\n  .ac .menu { margin-top: 7px; border: 1px solid var(--line); border-radius: 8px; overflow: hidden;\n    max-width: 260px; }\n  .ac .item { padding: 5px 10px; color: var(--ink); border-bottom: 1px solid var(--line); }\n  .ac .item:last-child { border-bottom: 0; }\n  .ac .item .kt { color: var(--muted); }\n  .ac .empty { padding: 7px 10px; color: var(--muted); font-style: italic; font-family: var(--fntsys, inherit);\n    font-size: .8rem; }\n\n  \u002F* validation *\u002F\n  .valline { font-size: .9rem; line-height: 1.45; display: flex; gap: 8px; align-items: flex-start; }\n  .valline .ic { font-weight: 800; flex: none; }\n  .val-ok  { color: var(--good); }\n  .val-err { color: var(--err); }\n  .val-warn{ color: var(--bad); }\n  .val-neutral { color: var(--muted); }\n  .valline code { color: inherit; }\n\n  \u002F* status bar *\u002F\n  .status { border-radius: 12px; padding: 12px 14px; font-size: .88rem; line-height: 1.5;\n    border: 1px solid var(--line); background: #7c9cff12; }\n  .status b { color: var(--ink); }\n  .status code { color: var(--accent); background: #ffffff10; padding: 1px 5px; border-radius: 5px; }\n  .status code.lit { color: var(--accent); }\n\n  @media (max-width: 560px) {\n    .seg button { font-size: .74rem; padding: 8px 5px; }\n    .seg button .mono { font-size: .7rem; }\n  }","\"use strict\";\n  const $ = (id) => document.getElementById(id);\n\n  \u002F\u002F The config every mode declares. Values are one of the app's known routes.\n  const LIT = { home: \"'\u002F'\", about: \"'\u002Fabout'\", settings: \"'\u002Fsettings'\" };\n  const BROKEN_ABOUT = \"'\u002Fabuot'\";                       \u002F\u002F a typo not in the Route union\n  const ROUTE_UNION = \"'\u002F' | '\u002Fabout' | '\u002Fsettings' | '\u002Flogin'\";\n  const KEY_UNION = \"'home' | 'about' | 'settings'\";\n\n  const state = { mode: \"annot\", prop: \"home\", broken: false };\n\n  \u002F\u002F ---- code block rendering (syntax-coloured, reflects mode + broken) --------\n  function esc(s){ return s.replace(\u002F&\u002Fg,\"&amp;\").replace(\u002F\u003C\u002Fg,\"&lt;\").replace(\u002F>\u002Fg,\"&gt;\"); }\n\n  function renderCode() {\n    const b = state.broken;\n    \u002F\u002F the three object-literal value lines (about turns red + squiggly when broken)\n    const aboutStr = b\n      ? `\u003Cspan class=\"bad\">'\u002Fabuot'\u003C\u002Fspan>`\n      : `\u003Cspan class=\"str\">'\u002Fabout'\u003C\u002Fspan>`;\n    const body =\n`  \u003Cspan class=\"pr\">home\u003C\u002Fspan>:     \u003Cspan class=\"str\">'\u002F'\u003C\u002Fspan>,\n  \u003Cspan class=\"pr\">about\u003C\u002Fspan>:    ${aboutStr},\n  \u003Cspan class=\"pr\">settings\u003C\u002Fspan>: \u003Cspan class=\"str\">'\u002Fsettings'\u003C\u002Fspan>,`;\n\n    const TYPE = `\u003Cspan class=\"ty\">Record\u003C\u002Fspan>&lt;\u003Cspan class=\"ty\">string\u003C\u002Fspan>, \u003Cspan class=\"ty\">Route\u003C\u002Fspan>&gt;`;\n    const head =\n`\u003Cspan class=\"cm\">\u002F\u002F each screen maps to one of the app's known routes\u003C\u002Fspan>\n\u003Cspan class=\"kw\">type\u003C\u002Fspan> \u003Cspan class=\"ty\">Route\u003C\u002Fspan> = \u003Cspan class=\"str\">'\u002F'\u003C\u002Fspan> | \u003Cspan class=\"str\">'\u002Fabout'\u003C\u002Fspan> | \u003Cspan class=\"str\">'\u002Fsettings'\u003C\u002Fspan> | \u003Cspan class=\"str\">'\u002Flogin'\u003C\u002Fspan>;\n\n`;\n    let open, close;\n    if (state.mode === \"bare\") {\n      open  = `\u003Cspan class=\"kw\">const\u003C\u002Fspan> \u003Cspan class=\"pr\">NAV\u003C\u002Fspan> = {`;\n      close = `};`;\n    } else if (state.mode === \"annot\") {\n      open  = `\u003Cspan class=\"kw\">const\u003C\u002Fspan> \u003Cspan class=\"pr\">NAV\u003C\u002Fspan>: ${TYPE} = {`;\n      close = `};`;\n    } else {\n      open  = `\u003Cspan class=\"kw\">const\u003C\u002Fspan> \u003Cspan class=\"pr\">NAV\u003C\u002Fspan> = {`;\n      close = `} \u003Cspan class=\"kw\">satisfies\u003C\u002Fspan> ${TYPE};`;\n    }\n    $(\"code\").innerHTML = head + open + \"\\n\" + body + \"\\n\" + close;\n  }\n\n  \u002F\u002F ---- per-mode data table (all verified against real TS 5.9) ----------------\n  const MODES = {\n    bare: {\n      \u002F\u002F value type of the inspected property\n      value: () => ({ html: `\u003Cspan class=\"wide\">string\u003C\u002Fspan>`, keep: false,\n        cap: `Widened to \u003Ccode>string\u003C\u002Fcode>. The literal is gone — and nothing was checked.` }),\n      keysType: `\u003Cspan class=\"wide tyname\">${esc(KEY_UNION)}\u003C\u002Fspan>`,\n      keys: \"exact\",\n      keysCap: `Exact keys survive, so autocomplete works — but see Validation.`,\n      status: `\u003Cb>Bare inference\u003C\u002Fb> keeps the literal keys (autocomplete works), but each value widens to \u003Ccode>string\u003C\u002Fcode> and TypeScript checks \u003Cb>nothing\u003C\u002Fb> — a wrong path compiles clean.`\n    },\n    annot: {\n      value: () => ({ html: `\u003Cspan class=\"wide tyname\">${esc(ROUTE_UNION)}\u003C\u002Fspan>`, keep: false,\n        cap: `Widened to the whole \u003Ccode>Route\u003C\u002Fcode> union. TypeScript knows it's \u003Cem>a\u003C\u002Fem> route — not \u003Cem>which\u003C\u002Fem> one.` }),\n      keysType: `\u003Cspan class=\"wide\">string\u003C\u002Fspan>`,\n      keys: \"none\",\n      keysCap: `Keys collapsed to \u003Ccode>string\u003C\u002Fcode> — no key suggestions, and \u003Ccode>keyof\u003C\u002Fcode> is useless.`,\n      status: `The \u003Cb>annotation\u003C\u002Fb> validates every value, but it swaps your object's type for the declared one: keys become \u003Ccode>string\u003C\u002Fcode> and values widen to the full union. → Click \u003Cb>satisfies\u003C\u002Fb> to keep them.`\n    },\n    satisfies: {\n      value: (p) => ({ html: `\u003Cspan class=\"lit\">${esc(LIT[p])}\u003C\u002Fspan>`, keep: true,\n        cap: `The exact literal, preserved. You get precision \u003Cem>and\u003C\u002Fem> the check.` }),\n      keysType: `\u003Cspan class=\"lit\">${esc(KEY_UNION)}\u003C\u002Fspan>`,\n      keys: \"exact\",\n      keysCap: `Exact keys → real autocomplete and a precise \u003Ccode>keyof typeof NAV\u003C\u002Fcode>.`,\n      \u002F\u002F status is rebuilt per-property in render() so the literal matches the inspected key\n      status: ``\n    }\n  };\n\n  \u002F\u002F ---- render everything from state -----------------------------------------\n  function render() {\n    \u002F\u002F segmented control\n    document.querySelectorAll(\".seg button\").forEach((btn) => {\n      btn.setAttribute(\"aria-pressed\", String(btn.dataset.mode === state.mode));\n    });\n    \u002F\u002F inspect pills\n    document.querySelectorAll(\".pill\").forEach((btn) => {\n      btn.setAttribute(\"aria-pressed\", String(btn.dataset.prop === state.prop));\n    });\n\n    renderCode();\n    const m = MODES[state.mode];\n\n    \u002F\u002F resolved value type\n    const v = m.value(state.prop);\n    $(\"valType\").innerHTML =\n      `\u003Cspan class=\"wide\">NAV.${state.prop}\u003C\u002Fspan> \u003Cspan class=\"arrow\">→\u003C\u002Fspan> ${v.html} ` +\n      `\u003Cspan class=\"badge ${v.keep ? \"keep\" : \"lost\"}\">${v.keep ? \"precision kept\" : \"precision lost\"}\u003C\u002Fspan>`;\n    $(\"valCap\").innerHTML = v.cap;\n\n    \u002F\u002F keys + autocomplete\n    $(\"keysType\").innerHTML = `\u003Cspan class=\"arrow\">keyof typeof NAV →\u003C\u002Fspan> ${m.keysType}`;\n    if (m.keys === \"exact\") {\n      $(\"acLabel\").innerHTML = `\u003Cspan style=\"color:var(--accent)\">▍\u003C\u002Fspan>`;\n      $(\"acMenu\").innerHTML = [\"home\",\"about\",\"settings\"].map((k) =>\n        `\u003Cdiv class=\"item\">${k} \u003Cspan class=\"kt\">: ${state.mode===\"satisfies\" ? esc(LIT[k]) : \"string\"}\u003C\u002Fspan>\u003C\u002Fdiv>`\n      ).join(\"\");\n    } else {\n      $(\"acLabel\").innerHTML = `\u003Cspan style=\"color:var(--muted)\">▍\u003C\u002Fspan>`;\n      $(\"acMenu\").innerHTML = `\u003Cdiv class=\"empty\">no key suggestions — any string is allowed\u003C\u002Fdiv>`;\n    }\n    $(\"keysCap\").innerHTML = m.keysCap;\n\n    \u002F\u002F validation\n    renderValidation();\n\n    \u002F\u002F status\n    let statusHtml = m.status;\n    if (state.mode === \"satisfies\") {\n      statusHtml = `\u003Cb>\u003Ccode>satisfies\u003C\u002Fcode>\u003C\u002Fb> runs the same check as the annotation, then hands your object's own type back — exact keys \u003Cb>and\u003C\u002Fb> the precise value \u003Ccode class=\"lit\">${LIT[state.prop]}\u003C\u002Fcode>. Validation with zero precision lost.`;\n    }\n    if (state.broken) {\n      const extra = state.mode === \"bare\"\n        ? ` \u003Cb style=\"color:var(--bad)\">Right now the typo slips through unnoticed.\u003C\u002Fb>`\n        : ` \u003Cb style=\"color:var(--good)\">And the typo you added is caught below.\u003C\u002Fb>`;\n      statusHtml += extra;\n    }\n    $(\"status\").innerHTML = statusHtml;\n  }\n\n  function renderValidation() {\n    const el = $(\"valid\"), cap = $(\"validCap\");\n    const validates = state.mode !== \"bare\";\n    if (state.broken) {\n      if (validates) {\n        el.className = \"valline val-err\";\n        el.innerHTML = `\u003Cspan class=\"ic\">✗\u003C\u002Fspan>\u003Cspan>Type error: Type \u003Ccode>'\u002Fabuot'\u003C\u002Fcode> is not assignable to type \u003Ccode>Route\u003C\u002Fcode>. Did you mean \u003Ccode>'\u002Fabout'\u003C\u002Fcode>?\u003C\u002Fspan>`;\n        cap.innerHTML = state.mode === \"satisfies\"\n          ? `\u003Ccode>satisfies\u003C\u002Fcode> caught it — \u003Cb>without giving up any of the precision above.\u003C\u002Fb>`\n          : `The annotation caught it too — validation is not what you lose with \u003Ccode>: Type\u003C\u002Fcode>.`;\n      } else {\n        el.className = \"valline val-warn\";\n        el.innerHTML = `\u003Cspan class=\"ic\">⚠\u003C\u002Fspan>\u003Cspan>No error. \u003Ccode>'\u002Fabuot'\u003C\u002Fcode> compiles — bare inference never validated it.\u003C\u002Fspan>`;\n        cap.innerHTML = `This is the trap: the bug ships silently. \u003Cb>Switch to annotation or satisfies\u003C\u002Fb> to catch it.`;\n      }\n    } else {\n      if (validates) {\n        el.className = \"valline val-ok\";\n        el.innerHTML = `\u003Cspan class=\"ic\">✓\u003C\u002Fspan>\u003Cspan>All values are valid routes.\u003C\u002Fspan>`;\n        cap.innerHTML = `Every value was checked against \u003Ccode>Route\u003C\u002Fcode>. Hit \u003Cb>Break it\u003C\u002Fb> to see the check fire.`;\n      } else {\n        el.className = \"valline val-neutral\";\n        el.innerHTML = `\u003Cspan class=\"ic\">○\u003C\u002Fspan>\u003Cspan>Compiles — but nothing was checked.\u003C\u002Fspan>`;\n        cap.innerHTML = `Bare inference performs no validation. Hit \u003Cb>Break it\u003C\u002Fb> and the typo still compiles.`;\n      }\n    }\n  }\n\n  \u002F\u002F ---- wiring ----------------------------------------------------------------\n  document.querySelectorAll(\".seg button\").forEach((btn) => {\n    btn.addEventListener(\"click\", () => { state.mode = btn.dataset.mode; render(); });\n  });\n  document.querySelectorAll(\".pill\").forEach((btn) => {\n    btn.addEventListener(\"click\", () => { state.prop = btn.dataset.prop; render(); });\n  });\n  $(\"breakBtn\").addEventListener(\"click\", () => {\n    state.broken = !state.broken;\n    $(\"breakBtn\").dataset.on = String(state.broken);\n    $(\"breakBtn\").setAttribute(\"aria-pressed\", String(state.broken));\n    $(\"breakBtn\").textContent = state.broken ? \"🔧 Fix it\" : \"💥 Break it (typo a path)\";\n    if (state.broken) state.prop = \"about\";            \u002F\u002F draw the eye to the broken value\n    render();\n  });\n  $(\"resetBtn\").addEventListener(\"click\", () => {\n    state.mode = \"annot\"; state.prop = \"home\"; state.broken = false;\n    $(\"breakBtn\").dataset.on = \"false\";\n    $(\"breakBtn\").setAttribute(\"aria-pressed\", \"false\");\n    $(\"breakBtn\").textContent = \"💥 Break it (typo a path)\";\n    render();\n  });\n\n  render();",{"height":40,"autorun":17},520,{"slug":33,"title":42,"excerpt":43,"coverUrl":44},"Your TypeScript config objects are losing type information. `satisfies` fixes it.","Type annotations widen your object types and erase the precise values TypeScript inferred. The `satisfies` operator validates against a type without changing what TypeScript keeps — you get the check without the widening.","\u002Fmedia\u002Fcovers\u002Ftypescript-satisfies-operator.png"]