[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"playground-weekly-javascript-proxy-reflect":44,"search-suggestions":58},[4,20,32],{"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","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","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.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"slug":45,"title":46,"summary":47,"html":48,"css":49,"js":50,"config":51,"doc":53},"weekly-javascript-proxy-reflect","JavaScript Proxy traps, live",null,"\u003Cdiv class=\"wrap\">\n    \u003Ch1>🎮 Proxy Trap Inspector — try it\u003C\u002Fh1>\n    \u003Cp class=\"sub\">\n      This isn't a simulation — it's a real \u003Ccode>new Proxy(target, handler)\u003C\u002Fcode> running in this page,\n      with a \u003Ccode>set\u003C\u002Fcode> trap that validates every write and notifies subscribers when a value\n      actually changes (the same mechanism Vue 3's reactivity system is built on).\n    \u003C\u002Fp>\n\n    \u003Cdiv class=\"panel\">\n      \u003Ch2>Write to the proxied state\u003C\u002Fh2>\n      \u003Cdiv class=\"row\">\n        \u003Clabel for=\"ageInput\">age\u003C\u002Flabel>\n        \u003Cinput type=\"text\" id=\"ageInput\" value=\"31\" inputmode=\"numeric\" \u002F>\n        \u003Cbutton id=\"setAge\">Set age\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n      \u003Cp class=\"caption\">\n        The \u003Ccode>age\u003C\u002Fcode> field is guarded by a validation rule: \u003Ccode>typeof v === \"number\" &amp;&amp; v &gt;= 0\u003C\u002Fcode>.\n        Try a negative number or type letters — the \u003Ccode>set\u003C\u002Fcode> trap rejects it before the object ever changes.\n      \u003C\u002Fp>\n      \u003Cdiv class=\"row\" style=\"margin-top: 12px;\">\n        \u003Cbutton class=\"preset\" data-preset=\"valid\">Preset: valid (33)\u003C\u002Fbutton>\n        \u003Cbutton class=\"preset\" data-preset=\"invalid-negative\">Preset: negative (-5)\u003C\u002Fbutton>\n        \u003Cbutton class=\"preset\" data-preset=\"invalid-type\">Preset: not a number (\"old\")\u003C\u002Fbutton>\n        \u003Cbutton class=\"ghost\" id=\"reset\">Reset log\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Ch2>Live proxied object\u003C\u002Fh2>\n      \u003Cdiv class=\"state\">\n        \u003Cspan>name: \u003Cb id=\"nameVal\">Ada\u003C\u002Fb>\u003C\u002Fspan>\n        \u003Cspan>age: \u003Cb id=\"ageVal\">31\u003C\u002Fb>\u003C\u002Fspan>\n        \u003Cspan>writes accepted: \u003Cb id=\"acceptCount\">0\u003C\u002Fb>\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Ch2>Trap activity log\u003C\u002Fh2>\n      \u003Cdiv class=\"log\" id=\"log\">\u003C\u002Fdiv>\n      \u003Cp class=\"status\" id=\"status\">Status: \u003Cb class=\"ok\">idle — try setting age above\u003C\u002Fb>\u003C\u002Fp>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>",":root {\n    --bg: #0f1115; --panel: #171a21; --ink: #e8ebf2; --muted: #99a1b3;\n    --accent: #6ea8fe; --good: #3fb950; --bad: #f85149; --line: #262b36;\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; justify-content: center; padding: 20px 16px; }\n  .wrap { width: 100%; max-width: 640px; }\n  h1 { font-size: 1.2rem; margin: 0 0 2px; }\n  .sub { color: var(--muted); font-size: .88rem; margin: 0 0 16px; line-height: 1.4; }\n  .panel { background: var(--panel); border: 1px solid var(--line); border-radius: 14px;\n    padding: 18px; margin-bottom: 14px; }\n  .panel h2 { font-size: .78rem; text-transform: uppercase; letter-spacing: .06em;\n    color: var(--muted); margin: 0 0 12px; font-weight: 700; }\n  .row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 10px; }\n  .row:last-child { margin-bottom: 0; }\n  label { color: var(--muted); font-size: .85rem; min-width: 46px; }\n  input[type=\"text\"], input[type=\"number\"] {\n    font: inherit; background: #0b0d10; color: var(--ink); border: 1px solid var(--line);\n    border-radius: 8px; padding: 8px 10px; width: 110px;\n  }\n  input:focus-visible, button:focus-visible {\n    outline: 2px solid var(--accent); outline-offset: 2px;\n  }\n  button { font: inherit; font-weight: 600; color: #0b0d10; background: var(--accent);\n    border: 0; border-radius: 10px; padding: 8px 14px; cursor: pointer; }\n  button.ghost { background: transparent; color: var(--ink); border: 1px solid var(--line); }\n  button.preset { background: #232838; color: var(--ink); border: 1px solid var(--line);\n    font-weight: 500; font-size: .82rem; padding: 7px 11px; }\n  .caption { color: var(--muted); font-size: .78rem; margin: 6px 0 0; line-height: 1.4; }\n  .state {\n    display: flex; gap: 18px; font-variant-numeric: tabular-nums; font-size: .95rem;\n    border-top: 1px dashed var(--line); padding-top: 12px; margin-top: 4px;\n  }\n  .state b { color: var(--accent); }\n  .log {\n    background: #0b0d10; border: 1px solid var(--line); border-radius: 10px;\n    padding: 10px 12px; height: 168px; overflow-y: auto; font: 12.5px\u002F1.55\n    ui-monospace, \"SF Mono\", Menlo, Consolas, monospace;\n  }\n  .log .ok { color: var(--good); }\n  .log .err { color: var(--bad); }\n  .log .info { color: var(--muted); }\n  .log div { white-space: pre-wrap; word-break: break-word; }\n  .status { font-size: .82rem; color: var(--muted); margin-top: 8px; }\n  .status b.ok { color: var(--good); }\n  .status b.err { color: var(--bad); }","const $ = (id) => document.getElementById(id);\n  const logEl = $(\"log\"), statusEl = $(\"status\");\n  const nameVal = $(\"nameVal\"), ageVal = $(\"ageVal\"), acceptCount = $(\"acceptCount\");\n  let accepted = 0;\n\n  function logLine(text, cls) {\n    const div = document.createElement(\"div\");\n    div.className = cls || \"info\";\n    div.textContent = text;\n    logEl.appendChild(div);\n    logEl.scrollTop = logEl.scrollHeight;\n  }\n\n  function setStatus(text, cls) {\n    statusEl.innerHTML = `Status: \u003Cb class=\"${cls || \"\"}\">${text}\u003C\u002Fb>`;\n  }\n\n  \u002F\u002F The real thing: a validated + reactive Proxy, same pattern taught in Stage 5 of the post.\n  const rules = { age: (v) => typeof v === \"number\" && v >= 0 };\n  const subscribers = new Set();\n\n  const state = new Proxy(\n    { name: \"Ada\", age: 31 },\n    {\n      get(target, prop, receiver) {\n        return Reflect.get(target, prop, receiver);\n      },\n      set(target, prop, value, receiver) {\n        const rule = rules[prop];\n        if (rule && !rule(value)) {\n          logLine(`[set] ${String(prop)} = ${JSON.stringify(value)}  ❌ rejected — fails validation rule`, \"err\");\n          return false; \u002F\u002F trap rejects the write; state.age stays unchanged\n        }\n        const changed = target[prop] !== value;\n        const result = Reflect.set(target, prop, value, receiver);\n        logLine(`[set] ${String(prop)} = ${JSON.stringify(value)}  ✅ accepted`, \"ok\");\n        if (changed) subscribers.forEach((fn) => fn(prop, value));\n        return result;\n      },\n    }\n  );\n\n  subscribers.add((prop, value) => {\n    logLine(`[notify] subscriber ran because ${String(prop)} changed to ${JSON.stringify(value)}`, \"info\");\n    render();\n  });\n\n  function render() {\n    nameVal.textContent = state.name;\n    ageVal.textContent = state.age;\n    acceptCount.textContent = accepted;\n  }\n\n  function attemptSet(raw) {\n    \u002F\u002F Parse like a form field would — this is what lets \"old\" or \"-5\" reach the trap as-is.\n    const num = Number(raw);\n    const value = raw.trim() !== \"\" && !Number.isNaN(num) ? num : raw;\n    const before = state.age;\n    try {\n      \u002F\u002F Setting via the trap directly through assignment (this is what `set` intercepts).\n      state.age = value;\n    } catch (e) {\n      logLine(`[set] threw: ${e.message}`, \"err\");\n    }\n    if (state.age !== before) {\n      accepted++;\n      setStatus(`accepted — age is now ${state.age}`, \"ok\");\n    } else {\n      setStatus(`rejected — age is still ${state.age}`, \"err\");\n    }\n    render();\n  }\n\n  $(\"setAge\").addEventListener(\"click\", () => attemptSet($(\"ageInput\").value));\n\n  document.querySelectorAll(\".preset\").forEach((btn) => {\n    btn.addEventListener(\"click\", () => {\n      const preset = btn.dataset.preset;\n      const value =\n        preset === \"valid\" ? \"33\" : preset === \"invalid-negative\" ? \"-5\" : \"old\";\n      $(\"ageInput\").value = value;\n      attemptSet(value);\n    });\n  });\n\n  $(\"reset\").addEventListener(\"click\", () => {\n    logEl.innerHTML = \"\";\n    setStatus(\"log cleared\", \"ok\");\n  });\n\n  render();\n  logLine(\"Proxy created. get\u002Fset traps are live — every read and write above runs through them.\", \"info\");",{"height":52,"autorun":17},520,{"slug":45,"title":54,"excerpt":55,"coverUrl":56,"vertical":57},"JavaScript Proxy and Reflect: The Complete Guide","Learn JavaScript Proxy and Reflect from the ground up — traps, invariants, and reactive state — with worked examples and a copy-paste cheat sheet.","\u002Fmedia\u002Fcovers\u002Fweekly-javascript-proxy-reflect.png",{"slug":6},[59,63,67,71,75,79,83,87,91,94,98,101],{"slug":60,"name":61,"articles":62},"webdev","Webdev",64,{"slug":64,"name":65,"articles":66},"frontend","Frontend",57,{"slug":68,"name":69,"articles":70},"javascript","Javascript",54,{"slug":72,"name":73,"articles":74},"css","Css",24,{"slug":76,"name":77,"articles":78},"typescript","Typescript",10,{"slug":80,"name":81,"articles":82},"performance","Performance",9,{"slug":84,"name":85,"articles":86},"grammar","Grammar",6,{"slug":88,"name":89,"articles":90},"react","React",5,{"slug":92,"name":93,"articles":90},"tutorial","Tutorial",{"slug":95,"name":96,"articles":97},"node","Node",4,{"slug":99,"name":100,"articles":97},"html","Html",{"slug":102,"name":103,"articles":43},"ai","Ai"]