[{"data":1,"prerenderedAt":4079},["ShallowReactive",2],{"notes-to-self-slugs":3,"posts":364},[4,7,10,13,16,19,22,25,28,31,34,37,40,43,46,49,52,55,58,61,64,67,70,73,76,79,82,85,88,91,94,97,100,103,106,109,112,115,118,121,124,127,130,133,136,139,142,145,148,151,154,157,160,163,166,169,172,175,178,181,184,187,190,193,196,199,202,205,208,211,214,217,220,223,226,229,232,235,238,241,244,247,250,253,256,259,262,265,268,271,274,277,280,283,286,289,292,295,298,301,304,307,310,313,316,319,322,325,328,331,334,337,340,343,346,349,352,355,358,361],{"title":5,"slug":6},"3D Math/Graphics","3d-mathgraphics",{"title":8,"slug":9},"3d modeling tools and resources","3d-modeling-tools-and-resources",{"title":11,"slug":12},"3D Printing","3d-printing",{"title":14,"slug":15},"A Mathematical Expression Parser in TypeScript","a-mathematical-expression-parser-in-typescript",{"title":17,"slug":18},"Ad Hoc","ad-hoc",{"title":20,"slug":21},"AI: ChatGPT and the Nature of Intelligence","ai-chatgpt-and-the-nature-of-intelligence",{"title":23,"slug":24},"Apple ][ Stuff","apple-stuff",{"title":26,"slug":27},"Art You Like","art-you-like",{"title":29,"slug":30},"Artists","artists",{"title":32,"slug":33},"Big O (AKA Big Oh) notation","big-o-aka-big-oh-notation",{"title":35,"slug":36},"Big O Cheat Sheet","big-o-cheat-sheet",{"title":38,"slug":39},"Blender","blender",{"title":41,"slug":42},"Books about Business, Process","books-about-business-process",{"title":44,"slug":45},"Break a Concave Shape into Multiple Convex Shapes","break-a-concave-shape-into-multiple-convex-shapes",{"title":47,"slug":48},"C++","c",{"title":50,"slug":51},"C Template Library","c-template-library",{"title":53,"slug":54},"CMS","cms",{"title":56,"slug":57},"Color Tools","color-tools",{"title":59,"slug":60},"CSS: Flex, a Great Explanation","css-flex-a-great-explanation",{"title":62,"slug":63},"CSS - Line Height Unit","css-line-height-unit",{"title":65,"slug":66},"Data Editor","data-editor",{"title":68,"slug":69},"Data transformation tool","data-transformation-tool",{"title":71,"slug":72},"Deep Learning","deep-learning",{"title":74,"slug":75},"Do a Circle and a Line Intersect?","do-a-circle-and-a-line-intersect",{"title":77,"slug":78},"Dungeon Deep/Wicked Engine","dungeon-deepwicked-engine",{"title":80,"slug":81},"Electric Car","electric-car",{"title":83,"slug":84},"Fast Pseudo-random number generator","fast-pseudo-random-number-generator",{"title":86,"slug":87},"Fermi’s Paradox, the Drake Equation, and All That","fermis-paradox-the-drake-equation-and-all-that",{"title":89,"slug":90},"Fonts","fonts",{"title":92,"slug":93},"Frameworks and Libraries of Interest","frameworks-and-libraries-of-interest",{"title":95,"slug":96},"Game Dev","game-dev",{"title":98,"slug":99},"Game Idea - Lunar Lander + Motherload","game-idea-lunar-lander-motherload",{"title":101,"slug":102},"Git Cheat Sheet","git-cheat-sheet",{"title":104,"slug":105},"Godot Exploration","godot-exploration",{"title":107,"slug":108},"Google Apps for biggerplanet.com","google-apps-for-biggerplanetcom",{"title":110,"slug":111},"GUIs in Movies","guis-in-movies",{"title":113,"slug":114},"Guy Uses Blender for Classic 2001/Alien Greeble","guy-uses-blender-for-classic-2001alien-greeble",{"title":116,"slug":117},"HDHomerun Connect Duo","hdhomerun-connect-duo",{"title":119,"slug":120},"Heroku shutting down free tiers","heroku-shutting-down-free-tiers",{"title":122,"slug":123},"Home Improvement","home-improvement",{"title":125,"slug":126},"How to Promote Phone Game","how-to-promote-phone-game",{"title":128,"slug":129},"HTML Dialog Element","html-dialog-element",{"title":131,"slug":132},"Ice maker Repair","ice-maker-repair",{"title":134,"slug":135},"Illustrator => Affinity Designer","illustrator-affinity-designer",{"title":137,"slug":138},"Image, Picture, Visual Resources","image-picture-visual-resources",{"title":140,"slug":141},"Interactive Narrative Scripting Language","interactive-narrative-scripting-language",{"title":143,"slug":144},"Interview with Zachary Boerner, Branch Cut, TableRaven","interview-with-zachary-boerner-branch-cut-tableraven",{"title":146,"slug":147},"JavaScript crypto ","javascript-crypto",{"title":149,"slug":150},"Job search","job-search",{"title":152,"slug":153},"Jobs","jobs",{"title":155,"slug":156},"Keep macOS awake","keep-macos-awake",{"title":158,"slug":159},"Keyboards","keyboards",{"title":161,"slug":162},"Linear Interpolation (lerp)","linear-interpolation-lerp",{"title":164,"slug":165},"Lost Music","lost-music",{"title":167,"slug":168},"Making a Vue/Nuxt Module","making-a-vuenuxt-module",{"title":170,"slug":171},"Marketing","marketing",{"title":173,"slug":174},"Meta Tools","meta-tools",{"title":176,"slug":177},"Military Ranks","military-ranks",{"title":179,"slug":180},"Miscellaneous Digitized Manuscripts and Books for Source Material, Art","miscellaneous-digitized-manuscripts-and-books-for-source-material-art",{"title":182,"slug":183},"More ChatGPT","more-chatgpt",{"title":185,"slug":186},"Movies to Watch","movies-to-watch",{"title":188,"slug":189},"Music (for listening) and Movies/Shows to Watch","music-for-listening-and-moviesshows-to-watch",{"title":191,"slug":192},"Music (Learning, Playing)","music-learning-playing",{"title":194,"slug":195},"Music Resource","music-resource",{"title":197,"slug":198},"Mystery Theater","mystery-theater",{"title":200,"slug":201},"Numeric Input on Mobile","numeric-input-on-mobile",{"title":203,"slug":204},"Old Mac Emulation in Browser","old-mac-emulation-in-browser",{"title":206,"slug":207},"On \"Cancel Culture\"","on-cancel-culture",{"title":209,"slug":210},"On Optimism","on-optimism",{"title":212,"slug":213},"On Testing","on-testing",{"title":215,"slug":216},"Password/Passphrase Generator Idea","passwordpassphrase-generator-idea",{"title":218,"slug":219},"Patent Troll","patent-troll",{"title":221,"slug":222},"Plants","plants",{"title":224,"slug":225},"PlayKode Research","playkode-research",{"title":227,"slug":228},"Plots","plots",{"title":230,"slug":231},"Poetry, Inspirational ","poetry-inspirational",{"title":233,"slug":234},"Portable Screen/Monitor","portable-screenmonitor",{"title":236,"slug":237},"Procedural dungeon for rogue like","procedural-dungeon-for-rogue-like",{"title":239,"slug":240},"Products to Buy","products-to-buy",{"title":242,"slug":243},"Quest VR Floor Height Problem","quest-vr-floor-height-problem",{"title":245,"slug":246},"React","react",{"title":248,"slug":249},"Recipes/Techniques","recipestechniques",{"title":251,"slug":252},"Red Sea Navigation","red-sea-navigation",{"title":254,"slug":255},"Regex Tools","regex-tools",{"title":257,"slug":258},"Roche Brothers Wines","roche-brothers-wines",{"title":260,"slug":261},"Science to exploit and mangle for fiction","science-to-exploit-and-mangle-for-fiction",{"title":263,"slug":264},"SCP Tips","scp-tips",{"title":266,"slug":267},"SDL","sdl",{"title":269,"slug":270},"SETI Editorial: Probing for ETI's Probes in the Solar System","seti-editorial-probing-for-etis-probes-in-the-solar-system",{"title":272,"slug":273},"Setting up sublime to be like iA Writer","setting-up-sublime-to-be-like-ia-writer",{"title":275,"slug":276},"Shared Element Transition","shared-element-transition",{"title":278,"slug":279},"Simulation Hypothesis","simulation-hypothesis",{"title":281,"slug":282},"Solar","solar",{"title":284,"slug":285},"Songs to Sample","songs-to-sample",{"title":287,"slug":288},"Sound Effects","sound-effects",{"title":290,"slug":291},"SSH tips","ssh-tips",{"title":293,"slug":294},"Supabase","supabase",{"title":296,"slug":297},"SVG Tools","svg-tools",{"title":299,"slug":300},"SvS/NoS Reboot notes","svsnos-reboot-notes",{"title":302,"slug":303},"Swift Resources & Notes","swift-resources-notes",{"title":305,"slug":306},"The Original Spacewar!","the-original-spacewar",{"title":308,"slug":309},"The Snowflake Method For Designing A Novel","the-snowflake-method-for-designing-a-novel",{"title":311,"slug":312},"The Thirty-Seven Basic Dramatic Situations","the-thirty-seven-basic-dramatic-situations",{"title":314,"slug":315},"Three Magic Words","three-magic-words",{"title":317,"slug":318},"Three.js","threejs",{"title":320,"slug":321},"Tools & Books","tools-books",{"title":323,"slug":324},"Vector Rendering Engine (C++)","vector-rendering-engine-c",{"title":326,"slug":327},"VR","vr",{"title":329,"slug":330},"Vue 3, Nuxt, Nuxt Content, & TypeScript","vue-3-nuxt-nuxt-content-typescript",{"title":332,"slug":333},"Vue and Vue Adjacent","vue-and-vue-adjacent",{"title":335,"slug":336},"Web dev","web-dev",{"title":338,"slug":339},"WebAssembly (WASM)/Web Worker Notes","webassembly-wasmweb-worker-notes",{"title":341,"slug":342},"WebGPU","webgpu",{"title":344,"slug":345},"What God, Quantum Mechanics and Consciousness Have in Common","what-god-quantum-mechanics-and-consciousness-have-in-common",{"title":347,"slug":348},"what’s an entity component system?","whats-an-entity-component-system",{"title":350,"slug":351},"Whisper","whisper",{"title":353,"slug":354},"Whitelist websites for kid use of raspberry pi","whitelist-websites-for-kid-use-of-raspberry-pi",{"title":356,"slug":357},"Why You Will Marry the Wrong Person","why-you-will-marry-the-wrong-person",{"title":359,"slug":360},"Word order","word-order",{"title":362,"slug":363},"You are h…","you-are-h",[365,491,1620,1653,2546,2871,2906,2951,3071,4039],{"_path":366,"_dir":367,"_draft":368,"_partial":368,"_locale":369,"title":341,"description":370,"slug":342,"date":371,"dateString":372,"encrypted":368,"encryptedBody":373,"body":374,"_type":485,"_id":486,"_source":487,"_file":488,"_stem":489,"_extension":490},"/notes-to-self/webgpu","notes-to-self",false,"","https://cohost.org/mcc/post/1406157-i-want-to-talk-about-webgpu via https://news.ycombinator.com/item?id=35800988",1683129600000,"2023-05-3",null,{"type":375,"children":376,"toc":480},"root",[377,399,410,423,428,433,446,451,456,461,468],{"type":378,"tag":379,"props":380,"children":381},"element","p",{},[382,391,393],{"type":378,"tag":383,"props":384,"children":388},"a",{"href":385,"rel":386},"https://cohost.org/mcc/post/1406157-i-want-to-talk-about-webgpu",[387],"nofollow",[389],{"type":390,"value":385},"text",{"type":390,"value":392}," via ",{"type":378,"tag":383,"props":394,"children":397},{"href":395,"rel":396},"https://news.ycombinator.com/item?id=35800988",[387],[398],{"type":390,"value":395},{"type":378,"tag":379,"props":400,"children":401},{},[402,404],{"type":390,"value":403},"See ",{"type":378,"tag":383,"props":405,"children":408},{"href":406,"rel":407},"https://alain.xyz/blog/raw-webgpu",[387],[409],{"type":390,"value":406},{"type":378,"tag":379,"props":411,"children":412},{},[413,415,421],{"type":390,"value":414},"From tweet ",{"type":378,"tag":383,"props":416,"children":419},{"href":417,"rel":418},"https://twitter.com/mcclure111/status/1437890963272261634",[387],[420],{"type":390,"value":417},{"type":390,"value":422},":",{"type":378,"tag":379,"props":424,"children":425},{},[426],{"type":390,"value":427},"1a. require() and \"import\" are subtly semantically different. \"require\" is synchronous & can happen anytime. \"import\" is synchronous at the toplevel, and asynchronous other times (import()).",{"type":378,"tag":379,"props":429,"children":430},{},[431],{"type":390,"value":432},"1b. Node.js freaked out about this difference hard. Most web packers decided not to care",{"type":378,"tag":379,"props":434,"children":435},{},[436,438,444],{"type":390,"value":437},"2a. If you are targeting a web packer, or cispiler like babel, usually you can use both import and require interchangeably and not think about it. What this semantically does ",{"type":378,"tag":439,"props":440,"children":441},"em",{},[442],{"type":390,"value":443},"exactly",{"type":390,"value":445}," will be documented by your packer, and is unpredictable.",{"type":378,"tag":379,"props":447,"children":448},{},[449],{"type":390,"value":450},"2b. If you are targeting node, each file in your project MUST use EITHER require OR import, no mixing. You have two choices: Make your ENTIRE project (everything covered not counting dependencies) use import, no exceptions; or use require in \".js\" files and import in \".mjs\" files",{"type":378,"tag":379,"props":452,"children":453},{},[454],{"type":390,"value":455},"2c. The \".mjs\" solution is fiddly and poorly documented, and I recommend against it.",{"type":378,"tag":379,"props":457,"children":458},{},[459],{"type":390,"value":460},"3/TLDR: If you want to support both Node.js and a reasonable range of packers, my recommendation is: - Use only import in your project, never require - Put \"type\": \"module\" in your package.json This is future-proof, and the only solution that avoids frustrating fiddly edge cases.",{"type":378,"tag":462,"props":463,"children":465},"h3",{"id":464},"_2023-05-19",[466],{"type":390,"value":467},"2023-05-19",{"type":378,"tag":379,"props":469,"children":470},{},[471,478],{"type":378,"tag":383,"props":472,"children":475},{"href":473,"rel":474},"https://www.willusher.io/graphics/2023/05/16/0-to-gltf-first-mesh",[387],[476],{"type":390,"value":477},"From 0 to gkTF (model format) with WebGPU",{"type":390,"value":479}," Other articles in there too. Maybe will get to textures & lighting?",{"title":369,"searchDepth":481,"depth":481,"links":482},2,[483],{"id":464,"depth":484,"text":467},3,"markdown","content:notes-to-self:webgpu.md","content","notes-to-self/webgpu.md","notes-to-self/webgpu","md",{"_path":492,"_dir":367,"_draft":368,"_partial":368,"_locale":369,"title":245,"description":493,"slug":246,"date":494,"dateString":495,"encrypted":368,"encryptedBody":373,"body":496,"_type":485,"_id":1617,"_source":487,"_file":1618,"_stem":1619,"_extension":490},"/notes-to-self/react","WHEREAS I am out of work and vuejobs.com only lists 1 or 2 jobs I'm potentially suited for a month, and whereas I see many many more jobs of interest that require React knowledge, I am reluctantly going to learn it.",1682956800000,"2023.05.01",{"type":375,"children":497,"toc":1611},[498,508,514,519,524,537,542,551,556,565,584,589,600,613,624,631,636,680,691,725,734,739,748,753,759,764,769,778,788,793,798,803,807,816,821,832,837,846,851,860,865,870,900,905,914,920,925,934,939,944,953,958,963,1035,1044,1057,1066,1079,1090,1099,1105,1110,1118,1123,1143,1148,1153,1162,1167,1179,1190,1218,1223,1230,1235,1244,1249,1258,1263,1268,1273,1282,1287,1292,1297,1306,1311,1316,1325,1330,1339,1355,1360,1368,1378,1388,1397,1407,1412,1421,1429,1438,1457,1465,1474,1478,1486,1492,1497,1506,1511,1524,1529,1541],{"type":378,"tag":379,"props":499,"children":500},{},[501,506],{"type":378,"tag":439,"props":502,"children":503},{},[504],{"type":390,"value":505},"WHEREAS",{"type":390,"value":507}," I am out of work and vuejobs.com only lists 1 or 2 jobs I'm potentially suited for a month, and whereas I see many many more jobs of interest that require React knowledge, I am reluctantly going to learn it.",{"type":378,"tag":462,"props":509,"children":511},{"id":510},"notes",[512],{"type":390,"value":513},"Notes",{"type":378,"tag":379,"props":515,"children":516},{},[517],{"type":390,"value":518},"Components are called components. Exports function with object argument, the keys of which are \"props.\"",{"type":378,"tag":379,"props":520,"children":521},{},[522],{"type":390,"value":523},"Refs are part of \"state\"",{"type":378,"tag":525,"props":526,"children":531},"pre",{"className":527,"code":529,"language":530,"meta":369},[528],"language-ts","import { useState } from 'react';\n\nexport default function SomeComponent() {\n  const [name, setName] = useState('nathan')\n\n  function changeName(newName: string) {\n    setName(newName)\n  }\n}\n\n","ts",[532],{"type":378,"tag":533,"props":534,"children":535},"code",{"__ignoreMap":369},[536],{"type":390,"value":529},{"type":378,"tag":379,"props":538,"children":539},{},[540],{"type":390,"value":541},"What about objects? You can't mutate them. You have to copy them, e.g.",{"type":378,"tag":525,"props":543,"children":546},{"className":544,"code":545,"language":530,"meta":369},[528],"import { useState } from 'react';\n\nexport default function SomeComponent() {\n  const [bio, setBio] = useState({\n    name: 'nathan',\n    address: {\n      steet: '123 Fancy St.',\n      town: 'Lovelywood'\n    }\n  })\n\n  function changeTown(newTown: string) {\n    setBio({\n      ...bio,\n      address: {\n        ...bio.address,\n        town: newTown\n      }\n    })\n  }\n}\n",[547],{"type":378,"tag":533,"props":548,"children":549},{"__ignoreMap":369},[550],{"type":390,"value":545},{"type":378,"tag":379,"props":552,"children":553},{},[554],{"type":390,"value":555},"Which is a PITA. Immer is a package that makes it easier with magic.",{"type":378,"tag":525,"props":557,"children":560},{"className":558,"code":559,"language":530,"meta":369},[528],"import { useImmer } from 'use-immer';\n\nexport default function SomeComponent() {\n  const [bio, updateBio] = useImmer({\n    name: 'nathan',\n    address: {\n      steet: '123 Fancy St.',\n      town: 'Lovelywood'\n    }\n  })\n\n  function changeTown(newTown: string) {\n    updateBio( draft => {\n      draft.address.town = newTown\n    })\n  }\n}\n",[561],{"type":378,"tag":533,"props":562,"children":563},{"__ignoreMap":369},[564],{"type":390,"value":559},{"type":378,"tag":379,"props":566,"children":567},{},[568,570,576,578],{"type":390,"value":569},"Note the convention of using ",{"type":378,"tag":533,"props":571,"children":573},{"className":572},[],[574],{"type":390,"value":575},"update[Type]",{"type":390,"value":577}," instead of ",{"type":378,"tag":533,"props":579,"children":581},{"className":580},[],[582],{"type":390,"value":583},"set[Type]",{"type":378,"tag":379,"props":585,"children":586},{},[587],{"type":390,"value":588},"Works with arrays as well, e.g. you can mutate an object in the array.",{"type":378,"tag":379,"props":590,"children":591},{},[592,594],{"type":390,"value":593},"You can also pass the object instead of the function, which appears to work like standard ",{"type":378,"tag":533,"props":595,"children":597},{"className":596},[],[598],{"type":390,"value":599},"useState",{"type":378,"tag":379,"props":601,"children":602},{},[603,605,611],{"type":390,"value":604},"Also: you can apparently call the ",{"type":378,"tag":533,"props":606,"children":608},{"className":607},[],[609],{"type":390,"value":610},"set",{"type":390,"value":612}," function with a function parameter (instead of a value), which gets passed the current value. Like this:",{"type":378,"tag":525,"props":614,"children":619},{"className":615,"code":617,"language":618,"meta":369},[616],"language-js","const [messages, setMessages] = useState([]);\n// ... later\nsetMessages(msgs => [...msgs, receivedMessage]);\n","js",[620],{"type":378,"tag":533,"props":621,"children":622},{"__ignoreMap":369},[623],{"type":390,"value":617},{"type":378,"tag":625,"props":626,"children":628},"h4",{"id":627},"reducer",[629],{"type":390,"value":630},"Reducer",{"type":378,"tag":379,"props":632,"children":633},{},[634],{"type":390,"value":635},"Organizational tool to handle state. Uses \"dispatch\" and \"action\" but that does not imply a promise/asynchronous function. Convention is \"what happened\" (past tense) not \"do this\" (imperative). Action has no predefined structure?",{"type":378,"tag":637,"props":638,"children":639},"blockquote",{},[640,649],{"type":378,"tag":379,"props":641,"children":642},{},[643],{"type":378,"tag":644,"props":645,"children":646},"span",{},[647],{"type":390,"value":648},"!note",{"type":378,"tag":379,"props":650,"children":651},{},[652,654,660,662,669,671,678],{"type":390,"value":653},"Yes you were correct, no aync: \"",{"type":378,"tag":655,"props":656,"children":657},"strong",{},[658],{"type":390,"value":659},"Reducers must be pure.",{"type":390,"value":661}," Similar to ",{"type":378,"tag":383,"props":663,"children":666},{"href":664,"rel":665},"https://react.dev/learn/queueing-a-series-of-state-updates",[387],[667],{"type":390,"value":668},"state updater functions",{"type":390,"value":670},", reducers run during rendering! (Actions are queued until the next render.) This means that reducers ",{"type":378,"tag":383,"props":672,"children":675},{"href":673,"rel":674},"https://react.dev/learn/keeping-components-pure",[387],[676],{"type":390,"value":677},"must be pure",{"type":390,"value":679},"—same inputs always result in the same output. They should not send requests, schedule timeouts, or perform any side effects…\"",{"type":378,"tag":379,"props":681,"children":682},{},[683,685],{"type":390,"value":684},"Reducer takes two arguments: the thing to change, and the action to perform. The thing to change is the only thing that should be changed. Same rules apply as to calling ",{"type":378,"tag":533,"props":686,"children":688},{"className":687},[],[689],{"type":390,"value":690},"setState",{"type":378,"tag":379,"props":692,"children":693},{},[694,696,702,704,709,711,716,718,723],{"type":390,"value":695},"Why \"reducer\" (hate it)? Named after array's ",{"type":378,"tag":533,"props":697,"children":699},{"className":698},[],[700],{"type":390,"value":701},"reduce",{"type":390,"value":703}," function. \"React reducers are an example of the same idea: they take the ",{"type":378,"tag":439,"props":705,"children":706},{},[707],{"type":390,"value":708},"state so far",{"type":390,"value":710}," and the ",{"type":378,"tag":439,"props":712,"children":713},{},[714],{"type":390,"value":715},"action",{"type":390,"value":717},", and return the ",{"type":378,"tag":439,"props":719,"children":720},{},[721],{"type":390,"value":722},"next state.",{"type":390,"value":724},"\" Lame. Encapsulates why you don't like react. Non-ergonomic. Non-thoughtful naming.",{"type":378,"tag":525,"props":726,"children":729},{"className":727,"code":728,"language":530,"meta":369},[528],"import { useReducer } from 'react';\n\nexport default function SomeComponent() {\n  const [tasks, dispatch] = useReducer(\n    tasksReducer,\n    initialTasks\n  )\n  // e.g.\n  function handleAddTask(name) {\n    dispatch({\n      type: 'added', id: nextId++, text: name\n    })\n  }\n}\n\nfunction tasksReducer(tasks, action) {\n  switch (action.type) {\n    case 'added': {\n      return [...tasks, {\n        id: action.id,\n        text: action.text,\n        done: false\n      }];\n    }\n    // ... etc.\n}\n",[730],{"type":378,"tag":533,"props":731,"children":732},{"__ignoreMap":369},[733],{"type":390,"value":728},{"type":378,"tag":379,"props":735,"children":736},{},[737],{"type":390,"value":738},"And yes there is an \"Immer\" flavor:",{"type":378,"tag":525,"props":740,"children":743},{"className":741,"code":742,"language":530,"meta":369},[528],"import { useImmerReducer } from 'use-immer';\n",[744],{"type":378,"tag":533,"props":745,"children":746},{"__ignoreMap":369},[747],{"type":390,"value":742},{"type":378,"tag":379,"props":749,"children":750},{},[751],{"type":390,"value":752},"So you can mutate objects/arrays/etc. (but only if it's the first argument!)",{"type":378,"tag":625,"props":754,"children":756},{"id":755},"context",[757],{"type":390,"value":758},"Context",{"type":378,"tag":379,"props":760,"children":761},{},[762],{"type":390,"value":763},"To provide state across component hierarchy.",{"type":378,"tag":379,"props":765,"children":766},{},[767],{"type":390,"value":768},"e.g.",{"type":378,"tag":525,"props":770,"children":773},{"className":771,"code":772,"language":530,"meta":369},[528],"\n// LevelContext.js\n\nimport { createContext } from 'react';\n\nexport const LevelContext = createContext(0);\n\n// Section.js\n\nimport { useContext } from 'react';\nimport { LevelContext } from './LevelContext.js';\n\nexport default function Section({ children }) {\n  const level = useContext(LevelContext);\n  return (\n    \u003Csection className=\"section\">\n      \u003CLevelContext.Provider value={level + 1}>\n        {children}\n      \u003C/LevelContext.Provider>\n    \u003C/section>\n  );\n}\n\n// Heading.js\nimport { useContext } from 'react';\nimport { LevelContext } from './LevelContext.js';\n\nexport default function Heading({ children }) {\n  const level = useContext(LevelContext);\n  switch (level) {\n    case 0:\n      throw Error('Heading must be inside a Section!');\n    case 1:\n      return \u003Ch1>{children}\u003C/h1>;\n    case 2:\n      return \u003Ch2>{children}\u003C/h2>;\n    case 3:\n      return \u003Ch3>{children}\u003C/h3>;\n    case 4:\n      return \u003Ch4>{children}\u003C/h4>;\n    case 5:\n      return \u003Ch5>{children}\u003C/h5>;\n    case 6:\n      return \u003Ch6>{children}\u003C/h6>;\n    default:\n      throw Error('Unknown level: ' + level);\n  }\n}\n\n// App.js\nimport Heading from './Heading.js';\nimport Section from './Section.js';\n\nexport default function Page() {\n  return (\n    \u003CSection>\n      \u003CHeading>Title\u003C/Heading>\n      \u003CSection>\n        \u003CHeading>Heading\u003C/Heading>\n        \u003CHeading>Heading\u003C/Heading>\n        \u003CHeading>Heading\u003C/Heading>\n        \u003CSection>\n          \u003CHeading>Sub-heading\u003C/Heading>\n        \u003C/Section>\n      \u003C/Section>\n    \u003C/Section>\n  );\n",[774],{"type":378,"tag":533,"props":775,"children":776},{"__ignoreMap":369},[777],{"type":390,"value":772},{"type":378,"tag":379,"props":779,"children":780},{},[781],{"type":378,"tag":383,"props":782,"children":785},{"href":783,"rel":784},"https://react.dev/learn/managing-state#scaling-up-with-reducer-and-context",[387],[786],{"type":390,"value":787},"Reducer and Contexts are used in tandem",{"type":378,"tag":379,"props":789,"children":790},{},[791],{"type":390,"value":792},"What about routes? What about onMounted beforeUnmouted.",{"type":378,"tag":379,"props":794,"children":795},{},[796],{"type":390,"value":797},"Redux = store, pinia",{"type":378,"tag":379,"props":799,"children":800},{},[801],{"type":390,"value":802},"I don't think there's a computed equivalent. Since the whole function of a component is called with each update, \"computed\" values are basically every var that's not part of state.",{"type":378,"tag":379,"props":804,"children":805},{},[806],{"type":390,"value":768},{"type":378,"tag":525,"props":808,"children":811},{"className":809,"code":810,"language":530,"meta":369},[528],"const [firstName, setFirstName] = useState('Cosmo')\nconst [lastName, setLastName] = useState('Brown')\nconst fullName = `${firstName} ${lastName}` // set every render\n",[812],{"type":378,"tag":533,"props":813,"children":814},{"__ignoreMap":369},[815],{"type":390,"value":810},{"type":378,"tag":379,"props":817,"children":818},{},[819],{"type":390,"value":820},"\"children\" = \"slot\" -- as a magic parameter, e.g.",{"type":378,"tag":525,"props":822,"children":827},{"className":823,"code":825,"language":826,"meta":369},[824],"language-jsx","function Panel({ title, children }) {\n  const [isActive, setIsActive] = useState(false);\n  return (\n    \u003Csection className=\"panel\">\n      \u003Ch3>{title}\u003C/h3>\n      {isActive ? (\n        \u003Cp>{children}\u003C/p>\n      ) : (\n        \u003Cbutton onClick={() => setIsActive(true)}>\n          Show\n        \u003C/button>\n      )}\n    \u003C/section>\n  );\n}\n\nexport default function Accordion() {\n  return (\n    \u003C>\n      \u003Ch2>Almaty, Kazakhstan\u003C/h2>\n      \u003CPanel title=\"About\">\n        First panel content. This is \"children.\"\n      \u003C/Panel>\n      \u003CPanel title=\"Etymology\">\n        Second panel content. This is \"children.\"\n      \u003C/Panel>\n    \u003C/>\n  );\n}\n","jsx",[828],{"type":378,"tag":533,"props":829,"children":830},{"__ignoreMap":369},[831],{"type":390,"value":825},{"type":378,"tag":379,"props":833,"children":834},{},[835],{"type":390,"value":836},"You can pass... handler functions? As properties.",{"type":378,"tag":525,"props":838,"children":841},{"className":839,"code":840,"language":826,"meta":369},[824],"export default function ParentComponent() {\n  function \n  return (\n    \u003CChildComponent onTest={() => console.log(\"hello world\")} />\n  )\n}\n\nfunction ChildComponent({ onTest }) {\n  return (\n    \u003Cbutton onClick={ onTest }>Test\u003C/button>\n  )\n}\n\n",[842],{"type":378,"tag":533,"props":843,"children":844},{"__ignoreMap":369},[845],{"type":390,"value":840},{"type":378,"tag":379,"props":847,"children":848},{},[849],{"type":390,"value":850},"This is legal",{"type":378,"tag":525,"props":852,"children":855},{"className":853,"code":854,"language":826,"meta":369},[824],"export default function App() {\n  const counter = \u003CCounter />;\n  return (\n    \u003Cdiv>\n      {counter}\n      {counter}\n    \u003C/div>\n  );\n}\n",[856],{"type":378,"tag":533,"props":857,"children":858},{"__ignoreMap":369},[859],{"type":390,"value":854},{"type":378,"tag":379,"props":861,"children":862},{},[863],{"type":390,"value":864},"Where each instance is independent (they do not share state)",{"type":378,"tag":379,"props":866,"children":867},{},[868],{"type":390,"value":869},"Normally state is attached to place in tree. Use key to be more specific.",{"type":378,"tag":379,"props":871,"children":872},{},[873,875,881,883,890,892,898],{"type":390,"value":874},"functions that start with ",{"type":378,"tag":533,"props":876,"children":878},{"className":877},[],[879],{"type":390,"value":880},"use",{"type":390,"value":882}," are magic ",{"type":378,"tag":383,"props":884,"children":887},{"href":885,"rel":886},"https://react.dev/learn/reusing-logic-with-custom-hooks",[387],[888],{"type":390,"value":889},"Custom Hooks",{"type":390,"value":891}," and let you use other hooks, like ",{"type":378,"tag":533,"props":893,"children":895},{"className":894},[],[896],{"type":390,"value":897},"useContext",{"type":390,"value":899}," inside of them.",{"type":378,"tag":379,"props":901,"children":902},{},[903],{"type":390,"value":904},"The idea is that you combine contexts and reducers into a single file (for a single concern), like this:",{"type":378,"tag":525,"props":906,"children":909},{"className":907,"code":908,"language":826,"meta":369},[824],"\n// TasksContext.js\n\nimport { createContext, useContext, useReducer } from 'react';\n\nconst TasksContext = createContext(null);\n\nconst TasksDispatchContext = createContext(null);\n\nexport function TasksProvider({ children }) {\n  const [tasks, dispatch] = useReducer(\n    tasksReducer,\n    initialTasks\n  );\n\n  return (\n    \u003CTasksContext.Provider value={tasks}>\n      \u003CTasksDispatchContext.Provider value={dispatch}>\n        {children}\n      \u003C/TasksDispatchContext.Provider>\n    \u003C/TasksContext.Provider>\n  );\n}\n\nexport function useTasks() {\n  return useContext(TasksContext);\n}\n\nexport function useTasksDispatch() {\n  return useContext(TasksDispatchContext);\n}\n\nfunction tasksReducer(tasks, action) {\n  switch (action.type) {\n    case 'added': {\n      return [...tasks, {\n        id: action.id,\n        text: action.text,\n        done: false\n      }];\n    }\n    case 'changed': {\n      return tasks.map(t => {\n        if (t.id === action.task.id) {\n          return action.task;\n        } else {\n          return t;\n        }\n      });\n    }\n    case 'deleted': {\n      return tasks.filter(t => t.id !== action.id);\n    }\n    default: {\n      throw Error('Unknown action: ' + action.type);\n    }\n  }\n}\n\nconst initialTasks = [\n  { id: 0, text: 'Philosopher’s Path', done: true },\n  { id: 1, text: 'Visit the temple', done: false },\n  { id: 2, text: 'Drink matcha', done: false }\n];\n\n// App.js\n\nimport AddTask from './AddTask.js';\nimport TaskList from './TaskList.js';\nimport { TasksProvider } from './TasksContext.js';\n\nexport default function TaskApp() {\n  return (\n    \u003CTasksProvider>\n      \u003Ch1>Day off in Kyoto\u003C/h1>\n      \u003CAddTask />\n      \u003CTaskList />\n    \u003C/TasksProvider>\n  );\n}\n\n// AddTask.js, e.g.\n\nimport { useState } from 'react';\nimport { useTasksDispatch } from './TasksContext.js';\n\nexport default function AddTask() {\n  const [text, setText] = useState('');\n  const dispatch = useTasksDispatch();\n  return (\n    \u003C>\n      \u003Cinput\n        placeholder=\"Add task\"\n        value={text}\n        onChange={e => setText(e.target.value)}\n      />\n      \u003Cbutton onClick={() => {\n        setText('');\n        dispatch({\n          type: 'added',\n          id: nextId++,\n          text: text,\n        }); \n      }}>Add\u003C/button>\n    \u003C/>\n  );\n}\n\nlet nextId = 3;\n\n",[910],{"type":378,"tag":533,"props":911,"children":912},{"__ignoreMap":369},[913],{"type":390,"value":908},{"type":378,"tag":625,"props":915,"children":917},{"id":916},"refs",[918],{"type":390,"value":919},"Refs",{"type":378,"tag":379,"props":921,"children":922},{},[923],{"type":390,"value":924},"Refs are like state but do not trigger re-renders. Used like Vue refs, with this syntax:",{"type":378,"tag":525,"props":926,"children":929},{"className":927,"code":928,"language":618,"meta":369},[616],"const someRef = useRef(123)\n\n// ...later\n\nif (someRef.current === 123) {\n  // ...\n}\n",[930],{"type":378,"tag":533,"props":931,"children":932},{"__ignoreMap":369},[933],{"type":390,"value":928},{"type":378,"tag":379,"props":935,"children":936},{},[937],{"type":390,"value":938},"Typical use cases: timeout IDs, DOM element. You think, vars that persist over rerenders.",{"type":378,"tag":379,"props":940,"children":941},{},[942],{"type":390,"value":943},"Also, like vue, refs can magically refer to DOM elements.",{"type":378,"tag":525,"props":945,"children":948},{"className":946,"code":947,"language":826,"meta":369},[824],"import { useRef } from 'react';\n\nexport default function Form() {\n  const inputRef = useRef(null);\n\n  function handleClick() {\n    inputRef.current.focus();\n  }\n\n  return (\n    \u003C>\n      \u003Cinput ref={inputRef} />\n      \u003Cbutton onClick={handleClick}>\n        Focus the input\n      \u003C/button>\n    \u003C/>\n  );\n}\n",[949],{"type":378,"tag":533,"props":950,"children":951},{"__ignoreMap":369},[952],{"type":390,"value":947},{"type":378,"tag":379,"props":954,"children":955},{},[956],{"type":390,"value":957},"Note that you should not use such a ref during a render - that is, during the top-level stuff in the function. The dom does not exist yet.",{"type":378,"tag":379,"props":959,"children":960},{},[961],{"type":390,"value":962},"No free array of refs for dom. You can do it, but it looks like a PITA:",{"type":378,"tag":637,"props":964,"children":965},{},[966,984],{"type":378,"tag":379,"props":967,"children":968},{},[969,971,982],{"type":390,"value":970},"One possible way around this is to get a single ref to their parent element, and then use DOM manipulation methods like ",{"type":378,"tag":383,"props":972,"children":975},{"href":973,"rel":974},"https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll",[387],[976],{"type":378,"tag":533,"props":977,"children":979},{"className":978},[],[980],{"type":390,"value":981},"querySelectorAll",{"type":390,"value":983}," to “find” the individual child nodes from it. However, this is brittle and can break if your DOM structure changes.",{"type":378,"tag":379,"props":985,"children":986},{},[987,989,1002,1004,1016,1018,1024,1026,1033],{"type":390,"value":988},"Another solution is to ",{"type":378,"tag":655,"props":990,"children":991},{},[992,994,1000],{"type":390,"value":993},"pass a function to the ",{"type":378,"tag":533,"props":995,"children":997},{"className":996},[],[998],{"type":390,"value":999},"ref",{"type":390,"value":1001}," attribute.",{"type":390,"value":1003}," This is called a ",{"type":378,"tag":383,"props":1005,"children":1008},{"href":1006,"rel":1007},"https://react.dev/reference/react-dom/components/common#ref-callback",[387],[1009,1014],{"type":378,"tag":533,"props":1010,"children":1012},{"className":1011},[],[1013],{"type":390,"value":999},{"type":390,"value":1015}," callback.",{"type":390,"value":1017}," React will call your ref callback with the DOM node when it’s time to set the ref, and with ",{"type":378,"tag":533,"props":1019,"children":1021},{"className":1020},[],[1022],{"type":390,"value":1023},"null",{"type":390,"value":1025}," when it’s time to clear it. This lets you maintain your own array or a ",{"type":378,"tag":383,"props":1027,"children":1030},{"href":1028,"rel":1029},"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map",[387],[1031],{"type":390,"value":1032},"Map",{"type":390,"value":1034},", and access any ref by its index or some kind of ID.",{"type":378,"tag":525,"props":1036,"children":1039},{"className":1037,"code":1038,"language":826,"meta":369},[824],"import { useRef } from 'react';\n\nexport default function CatFriends() {\n  const itemsRef = useRef(null);\n\n  function scrollToId(itemId) {\n    const map = getMap();\n    const node = map.get(itemId);\n    node.scrollIntoView({\n      behavior: 'smooth',\n      block: 'nearest',\n      inline: 'center'\n    });\n  }\n\n  function getMap() {\n    if (!itemsRef.current) {\n      // Initialize the Map on first usage.\n      itemsRef.current = new Map();\n    }\n    return itemsRef.current;\n  }\n\n  return (\n    \u003C>\n      \u003Cnav>\n        \u003Cbutton onClick={() => scrollToId(0)}>\n          Tom\n        \u003C/button>\n        \u003Cbutton onClick={() => scrollToId(5)}>\n          Maru\n        \u003C/button>\n        \u003Cbutton onClick={() => scrollToId(9)}>\n          Jellylorum\n        \u003C/button>\n      \u003C/nav>\n      \u003Cdiv>\n        \u003Cul>\n          {catList.map(cat => (\n            \u003Cli\n              key={cat.id}\n              ref={(node) => {\n                const map = getMap();\n                if (node) {\n                  map.set(cat.id, node);\n                } else {\n                  map.delete(cat.id);\n                }\n              }}\n            >\n              \u003Cimg\n                src={cat.imageUrl}\n                alt={'Cat #' + cat.id}\n              />\n            \u003C/li>\n          ))}\n        \u003C/ul>\n      \u003C/div>\n    \u003C/>\n  );\n}\n\nconst catList = [];\nfor (let i = 0; i \u003C 10; i++) {\n  catList.push({\n    id: i,\n    imageUrl: 'https://placekitten.com/250/200?image=' + i\n  });\n}\n\n\n",[1040],{"type":378,"tag":533,"props":1041,"children":1042},{"__ignoreMap":369},[1043],{"type":390,"value":1038},{"type":378,"tag":379,"props":1045,"children":1046},{},[1047,1049,1055],{"type":390,"value":1048},"If you want to get a ref to a child component's dom element, you must use ",{"type":378,"tag":533,"props":1050,"children":1052},{"className":1051},[],[1053],{"type":390,"value":1054},"forwardRef",{"type":390,"value":1056},". Looks like this:",{"type":378,"tag":525,"props":1058,"children":1061},{"className":1059,"code":1060,"language":826,"meta":369},[824],"import { forwardRef, useRef } from 'react';\n\nconst MyInput = forwardRef((props, ref) => {\n  return \u003Cinput {...props} ref={ref} />;\n});\n\nexport default function Form() {\n  const inputRef = useRef(null);\n\n  function handleClick() {\n    inputRef.current.focus();\n  }\n\n  return (\n    \u003C>\n      \u003CMyInput ref={inputRef} />\n      \u003Cbutton onClick={handleClick}>\n        Focus the input\n      \u003C/button>\n    \u003C/>\n  );\n}\n\n",[1062],{"type":378,"tag":533,"props":1063,"children":1064},{"__ignoreMap":369},[1065],{"type":390,"value":1060},{"type":378,"tag":379,"props":1067,"children":1068},{},[1069,1071,1077],{"type":390,"value":1070},"For more control over what gets exposed, you can use ",{"type":378,"tag":533,"props":1072,"children":1074},{"className":1073},[],[1075],{"type":390,"value":1076},"useImperitiveHandle",{"type":390,"value":1078},"?",{"type":378,"tag":379,"props":1080,"children":1081},{},[1082,1084],{"type":390,"value":1083},"If you have an action that updates the dom, and you want to immediately act on it you can use ",{"type":378,"tag":533,"props":1085,"children":1087},{"className":1086},[],[1088],{"type":390,"value":1089},"flushSync",{"type":378,"tag":525,"props":1091,"children":1094},{"className":1092,"code":1093,"language":618,"meta":369},[616],"flushSync(() => {  \n  setTodos([ ...todos, newTodo]);  \n});  \n\nlistRef.current.lastChild.scrollIntoView();\n",[1095],{"type":378,"tag":533,"props":1096,"children":1097},{"__ignoreMap":369},[1098],{"type":390,"value":1093},{"type":378,"tag":625,"props":1100,"children":1102},{"id":1101},"useeffect",[1103],{"type":390,"value":1104},"useEffect",{"type":378,"tag":379,"props":1106,"children":1107},{},[1108],{"type":390,"value":1109},"More later, but appears to be sort of like a watcher? Or beforeMount() + beforeUnmount()?",{"type":378,"tag":637,"props":1111,"children":1112},{},[1113],{"type":378,"tag":379,"props":1114,"children":1115},{},[1116],{"type":390,"value":1117},"Components may mount, update, or unmount. An Effect can only do two things: to start synchronizing something, and later to stop synchronizing it. This cycle can happen multiple times if your Effect depends on props and state that change over time.",{"type":378,"tag":379,"props":1119,"children":1120},{},[1121],{"type":390,"value":1122},"So, it's like a watcher that: if it references a property that changes (what about a state?), and the property changes, then first the \"unmount\" function gets called, then the main function gets called with the new values.",{"type":378,"tag":637,"props":1124,"children":1125},{},[1126,1131],{"type":378,"tag":379,"props":1127,"children":1128},{},[1129],{"type":390,"value":1130},"Effects re-synchronize if any of the values they read, like props or state, are different than during last render. Sometimes, you want a mix of both behaviors: an Effect that re-runs in response to some values but not others.",{"type":378,"tag":379,"props":1132,"children":1133},{},[1134,1136,1141],{"type":390,"value":1135},"All code inside Effects is ",{"type":378,"tag":439,"props":1137,"children":1138},{},[1139],{"type":390,"value":1140},"reactive.",{"type":390,"value":1142}," It will run again if some reactive value it reads has changed due to a re-render.",{"type":378,"tag":379,"props":1144,"children":1145},{},[1146],{"type":390,"value":1147},"But if you call a function from within the effect function, it will NOT be magically watched.",{"type":378,"tag":379,"props":1149,"children":1150},{},[1151],{"type":390,"value":1152},"But yes it does appear to be where you add/remove event listeners as well.",{"type":378,"tag":525,"props":1154,"children":1157},{"className":1155,"code":1156,"language":618,"meta":369},[616],"import { useState, useEffect } from 'react';\n\nexport function usePointerPosition() {\n  const [position, setPosition] = useState({ x: 0, y: 0 });\n  useEffect(() => {\n    function handleMove(e) {\n      setPosition({ x: e.clientX, y: e.clientY });\n    }\n    window.addEventListener('pointermove', handleMove);\n    return () => window.removeEventListener('pointermove', handleMove);\n  }, []);\n  return position;\n}\n",[1158],{"type":378,"tag":533,"props":1159,"children":1160},{"__ignoreMap":369},[1161],{"type":390,"value":1156},{"type":378,"tag":379,"props":1163,"children":1164},{},[1165],{"type":390,"value":1166},"There is some way to specify dependencies? Yes in array argument.",{"type":378,"tag":379,"props":1168,"children":1169},{},[1170,1172,1177],{"type":390,"value":1171},"useEffect is run ",{"type":378,"tag":439,"props":1173,"children":1174},{},[1175],{"type":390,"value":1176},"after",{"type":390,"value":1178}," every render.",{"type":378,"tag":379,"props":1180,"children":1181},{},[1182,1184],{"type":390,"value":1183},"So, one use case, is to use useEffect to delay until after rendering, like ",{"type":378,"tag":533,"props":1185,"children":1187},{"className":1186},[],[1188],{"type":390,"value":1189},"onMount",{"type":378,"tag":637,"props":1191,"children":1192},{},[1193,1209],{"type":378,"tag":379,"props":1194,"children":1195},{},[1196,1201,1203,1208],{"type":378,"tag":644,"props":1197,"children":1198},{},[1199],{"type":390,"value":1200},"!warning",{"type":390,"value":1202},"\nYou shouldn't change state inside a ",{"type":378,"tag":533,"props":1204,"children":1206},{"className":1205},[],[1207],{"type":390,"value":1104},{"type":390,"value":1078},{"type":378,"tag":525,"props":1210,"children":1213},{"className":1211,"code":1212,"language":618,"meta":369},[616],"// infinite loop\nconst [count, setCount] = useState(0);  \nuseEffect(() => {  \n  setCount(count + 1);  \n});\n",[1214],{"type":378,"tag":533,"props":1215,"children":1216},{"__ignoreMap":369},[1217],{"type":390,"value":1212},{"type":378,"tag":379,"props":1219,"children":1220},{},[1221],{"type":390,"value":1222},"What if you had a mounted flag?",{"type":378,"tag":1224,"props":1225,"children":1227},"h5",{"id":1226},"declaring-dependency",[1228],{"type":390,"value":1229},"Declaring Dependency",{"type":378,"tag":379,"props":1231,"children":1232},{},[1233],{"type":390,"value":1234},"The second argument is an array of dependencies.",{"type":378,"tag":525,"props":1236,"children":1239},{"className":1237,"code":1238,"language":618,"meta":369},[616],"useEffect(() => {\n    // isPlaying is a property\n    if (isPlaying) {\n      console.log('Calling video.play()');\n      ref.current.play();\n    } else {\n      console.log('Calling video.pause()');\n      ref.current.pause();\n    }\n  }, []); // lint error: missing dependency: 'isPlaying'\n",[1240],{"type":378,"tag":533,"props":1241,"children":1242},{"__ignoreMap":369},[1243],{"type":390,"value":1238},{"type":378,"tag":379,"props":1245,"children":1246},{},[1247],{"type":390,"value":1248},"Ugh. Look:",{"type":378,"tag":525,"props":1250,"children":1253},{"className":1251,"code":1252,"language":618,"meta":369},[616],"useEffect(() => {\n  // This runs after every render\n});\n\nuseEffect(() => {\n  // This runs only on mount (when the component appears)\n}, []);\n\nuseEffect(() => {\n  // This runs on mount *and also* if either a or b have changed since the last render\n}, [a, b]);\n",[1254],{"type":378,"tag":533,"props":1255,"children":1256},{"__ignoreMap":369},[1257],{"type":390,"value":1252},{"type":378,"tag":379,"props":1259,"children":1260},{},[1261],{"type":390,"value":1262},"As implied before, the return value of the function passed to useEffect is the \"cleanup\" function. It gets called before each call to useEffect (after the first) and once on unmount.",{"type":378,"tag":379,"props":1264,"children":1265},{},[1266],{"type":390,"value":1267},"In development, useEffect is immediately called twice (this is to surface unmounting problems).",{"type":378,"tag":379,"props":1269,"children":1270},{},[1271],{"type":390,"value":1272},"For network requests, you need a way to ignore results of aborted requests, like this:",{"type":378,"tag":525,"props":1274,"children":1277},{"className":1275,"code":1276,"language":618,"meta":369},[616],"useEffect(() => {\n  let ignore = false;\n\n  async function startFetching() {\n    const json = await fetchTodos(userId);\n    if (!ignore) {\n      setTodos(json);\n    }\n  }\n\n  startFetching();\n\n  return () => {\n    ignore = true;\n  };\n}, [userId]);\n",[1278],{"type":378,"tag":533,"props":1279,"children":1280},{"__ignoreMap":369},[1281],{"type":390,"value":1276},{"type":378,"tag":379,"props":1283,"children":1284},{},[1285],{"type":390,"value":1286},"Note that the scope of ignore is within the returned function.",{"type":378,"tag":379,"props":1288,"children":1289},{},[1290],{"type":390,"value":1291},"Docs state that you should not use an effect \"if you want to update a component’s state when some props or state change\" — so what do you do?",{"type":378,"tag":379,"props":1293,"children":1294},{},[1295],{"type":390,"value":1296},"Here’s an example of reacting to a prop change.",{"type":378,"tag":525,"props":1298,"children":1301},{"className":1299,"code":1300,"language":618,"meta":369},[616],"// DO NOT DO THIS\nfunction List({ items }) {  \n  const [isReverse, setIsReverse] = useState(false);  \n  const [selection, setSelection] = useState(null);  \n\n  // 🔴 Avoid: Adjusting state on prop change in an Effect  \n  // this will cause 2 unnessary renders\n  useEffect(() => {  \n    setSelection(null);  \n  }, [items]);  \n// ...  \n}\n\n// DO THIS INSTEAD\nfunction List({ items }) {\n  const [isReverse, setIsReverse] = useState(false);\n  const [selection, setSelection] = useState(null);\n\n  // Better: Adjust the state while rendering\n  const [prevItems, setPrevItems] = useState(items);\n \n  if (items !== prevItems) {\n    setPrevItems(items);\n    setSelection(null);\n  }\n  // ...\n}\n",[1302],{"type":378,"tag":533,"props":1303,"children":1304},{"__ignoreMap":369},[1305],{"type":390,"value":1300},{"type":378,"tag":379,"props":1307,"children":1308},{},[1309],{"type":390,"value":1310},"** Why that works ** — state changes at the top, before any output, don’t trigger re-renders, same as initial assignment.",{"type":378,"tag":379,"props":1312,"children":1313},{},[1314],{"type":390,"value":1315},"But, even better, would be not to need it at all:",{"type":378,"tag":525,"props":1317,"children":1320},{"className":1318,"code":1319,"language":618,"meta":369},[616],"function List({ items }) {\n  const [isReverse, setIsReverse] = useState(false);\n  const [selectedId, setSelectedId] = useState(null);\n  // ✅ Best: Calculate everything during rendering\n  const selection = items.find(item => item.id === selectedId) ?? null;\n  // ...\n}\n",[1321],{"type":378,"tag":533,"props":1322,"children":1323},{"__ignoreMap":369},[1324],{"type":390,"value":1319},{"type":378,"tag":379,"props":1326,"children":1327},{},[1328],{"type":390,"value":1329},"Later, docs do offer this pattern for e.g. app init:",{"type":378,"tag":525,"props":1331,"children":1334},{"className":1332,"code":1333,"language":618,"meta":369},[616],"let didInit = false;\n\nfunction App() {\n  useEffect(() => {\n    if (!didInit) {\n      didInit = true;\n      // ✅ Only runs once per app load\n      loadDataFromLocalStorage();\n      checkAuthToken();\n    }\n  }, []);\n  // ...\n}\n\n// PROBABLY BETTER\n// Check if we're running in the browser.  \nif (typeof window !== 'undefined') { \n  // ✅ Only runs once per app load\n  loadDataFromLocalStorage();\n  checkAuthToken();  \n}  \n\nfunction App() {  \n// ...  \n}\n",[1335],{"type":378,"tag":533,"props":1336,"children":1337},{"__ignoreMap":369},[1338],{"type":390,"value":1333},{"type":378,"tag":637,"props":1340,"children":1341},{},[1342],{"type":378,"tag":379,"props":1343,"children":1344},{},[1345,1347,1353],{"type":390,"value":1346},"Code at the top level runs once when your component is imported—even if it doesn’t end up being rendered. To avoid slowdown or surprising behavior when importing arbitrary components, don’t overuse this pattern. Keep app-wide initialization logic to root component modules like ",{"type":378,"tag":533,"props":1348,"children":1350},{"className":1349},[],[1351],{"type":390,"value":1352},"App.js",{"type":390,"value":1354}," or in your application’s entry point.",{"type":378,"tag":379,"props":1356,"children":1357},{},[1358],{"type":390,"value":1359},"Later…",{"type":378,"tag":637,"props":1361,"children":1362},{},[1363],{"type":378,"tag":379,"props":1364,"children":1365},{},[1366],{"type":390,"value":1367},"Effects in your application should eventually be replaced by custom Hooks, whether written by you or by the community. Custom Hooks hide the synchronization logic, so the calling component doesn’t know about the Effect. As you keep working on your app, you’ll develop a palette of Hooks to choose from, and eventually you won’t need to write Effects in your components very often.",{"type":378,"tag":625,"props":1369,"children":1371},{"id":1370},"usememo",[1372],{"type":378,"tag":533,"props":1373,"children":1375},{"className":1374},[],[1376],{"type":390,"value":1377},"useMemo",{"type":378,"tag":379,"props":1379,"children":1380},{},[1381,1386],{"type":378,"tag":533,"props":1382,"children":1384},{"className":1383},[],[1385],{"type":390,"value":1377},{"type":390,"value":1387}," is react’s built-in memoizer that you can use for expensive calculations. Looks like this:",{"type":378,"tag":525,"props":1389,"children":1392},{"className":1390,"code":1391,"language":618,"meta":369},[616],"function TodoList({ todos, filter }) {\n  const [newTodo, setNewTodo] = useState('');\n  // immediately run during render\n  const visibleTodos = useMemo(() => {\n      return getFilteredTodos(todos, filter);\n    },\n    [todos, filter] // getFilteredTodos only gets called if either\n                    // of these change\n  );\n  // ...\n}\n",[1393],{"type":378,"tag":533,"props":1394,"children":1395},{"__ignoreMap":369},[1396],{"type":390,"value":1391},{"type":378,"tag":462,"props":1398,"children":1400},{"id":1399},"usesyncexternalstore",[1401],{"type":378,"tag":533,"props":1402,"children":1404},{"className":1403},[],[1405],{"type":390,"value":1406},"useSyncExternalStore",{"type":378,"tag":379,"props":1408,"children":1409},{},[1410],{"type":390,"value":1411},"You may be tempted to use an external store like this:",{"type":378,"tag":525,"props":1413,"children":1416},{"className":1414,"code":1415,"language":618,"meta":369},[616],"// note the magical `use` function, making it a hook.\nfunction useOnlineStatus() {\n  // Not ideal: Manual store subscription in an Effect\n  const [isOnline, setIsOnline] = useState(true);\n  // run once after component initial mount\n  useEffect(() => {\n    function updateState() {\n      setIsOnline(navigator.onLine);\n    }\n\n    updateState();\n\n    window.addEventListener('online', updateState);\n    window.addEventListener('offline', updateState);\n    return () => {\n      window.removeEventListener('online', updateState);\n      window.removeEventListener('offline', updateState);\n    };\n  }, []);\n  return isOnline;\n}\n\nfunction ChatIndicator() {\n  const isOnline = useOnlineStatus();\n  // ...\n}\n",[1417],{"type":378,"tag":533,"props":1418,"children":1419},{"__ignoreMap":369},[1420],{"type":390,"value":1415},{"type":378,"tag":637,"props":1422,"children":1423},{},[1424],{"type":378,"tag":379,"props":1425,"children":1426},{},[1427],{"type":390,"value":1428},"Although it’s common to use Effects for this, React has a purpose-built Hook for subscribing to an external store that is preferred instead.",{"type":378,"tag":525,"props":1430,"children":1433},{"className":1431,"code":1432,"language":618,"meta":369},[616],"function subscribe(callback) {\n  window.addEventListener('online', callback);\n  window.addEventListener('offline', callback);\n  return () => {\n    window.removeEventListener('online', callback);\n    window.removeEventListener('offline', callback);\n  };\n}\n\nfunction useOnlineStatus() {\n  // ✅ Good: Subscribing to an external store with a built-in Hook\n  return useSyncExternalStore(\n    subscribe, // React won't resubscribe for as long as you pass the same function\n    () => navigator.onLine, // How to get the value on the client\n    () => true // How to get the value on the server\n  );\n}\n\nfunction ChatIndicator() {\n  const isOnline = useOnlineStatus();\n  // ...\n}\n",[1434],{"type":378,"tag":533,"props":1435,"children":1436},{"__ignoreMap":369},[1437],{"type":390,"value":1432},{"type":378,"tag":379,"props":1439,"children":1440},{},[1441,1443,1448,1450,1455],{"type":390,"value":1442},"It ",{"type":378,"tag":439,"props":1444,"children":1445},{},[1446],{"type":390,"value":1447},"is",{"type":390,"value":1449}," sanctioned to use ",{"type":378,"tag":533,"props":1451,"children":1453},{"className":1452},[],[1454],{"type":390,"value":1104},{"type":390,"value":1456}," for updating a query, because the query may change without user action.",{"type":378,"tag":637,"props":1458,"children":1459},{},[1460],{"type":378,"tag":379,"props":1461,"children":1462},{},[1463],{"type":390,"value":1464},"If you don’t use a framework (and don’t want to build your own) but would like to make data fetching from Effects more ergonomic, consider extracting your fetching logic into a custom Hook like in this example:",{"type":378,"tag":525,"props":1466,"children":1469},{"className":1467,"code":1468,"language":618,"meta":369},[616],"function SearchResults({ query }) {\n  const [page, setPage] = useState(1);\n  const params = new URLSearchParams({ query, page });\n  const results = useData(`/api/search?${params}`);\n\n  function handleNextPageClick() {\n    setPage(page + 1);\n  }\n  // ...\n}\n\nfunction useData(url) {\n  const [data, setData] = useState(null);\n  useEffect(() => {\n    let ignore = false;\n    fetch(url)\n      .then(response => response.json())\n      .then(json => {\n        if (!ignore) {\n          setData(json);\n        }\n      });\n    return () => {\n      ignore = true;\n    };\n  }, [url]);\n  return data;\n}\n",[1470],{"type":378,"tag":533,"props":1471,"children":1472},{"__ignoreMap":369},[1473],{"type":390,"value":1468},{"type":378,"tag":1475,"props":1476,"children":1477},"hr",{},[],{"type":378,"tag":637,"props":1479,"children":1480},{},[1481],{"type":378,"tag":379,"props":1482,"children":1483},{},[1484],{"type":390,"value":1485},"Props, state, and variables declared inside your component’s body are called reactive values.",{"type":378,"tag":462,"props":1487,"children":1489},{"id":1488},"effect-events-not-yet-stable",[1490],{"type":390,"value":1491},"Effect Events - Not Yet Stable",{"type":378,"tag":379,"props":1493,"children":1494},{},[1495],{"type":390,"value":1496},"It’s a function that gets access to local vars but that won’t be considered reactive. For example:",{"type":378,"tag":525,"props":1498,"children":1501},{"className":1499,"code":1500,"language":618,"meta":369},[616],"\n// this won't work\n  function onConnected() {\n    showNotification('Connected!', theme);\n  }\n  \n  useEffect(() => {\n    const connection = createConnection(serverUrl, roomId);\n    connection.on('connected', () => {\n      onConnected();\n    });\n    connection.connect();\n    return () => connection.disconnect();\n  }, [roomId]); // linter complains that onConnected is ommitted\n\n// this will work\n  const onConnected = useEffectEvent(() => {\n    showNotification('Connected!', theme);\n  });\n  \n  useEffect(() => {\n    const connection = createConnection(serverUrl, roomId);\n    connection.on('connected', () => {\n      onConnected();\n    });\n    connection.connect();\n    return () => connection.disconnect();\n  }, [roomId]);\n",[1502],{"type":378,"tag":533,"props":1503,"children":1504},{"__ignoreMap":369},[1505],{"type":390,"value":1500},{"type":378,"tag":379,"props":1507,"children":1508},{},[1509],{"type":390,"value":1510},"they can only be called from within an effect, never passed to hooks or components.",{"type":378,"tag":379,"props":1512,"children":1513},{},[1514,1516,1522],{"type":390,"value":1515},"TODO: review ",{"type":378,"tag":383,"props":1517,"children":1520},{"href":1518,"rel":1519},"https://react.dev/learn/removing-effect-dependencies",[387],[1521],{"type":390,"value":1518},{"type":390,"value":1523}," - first thing you had trouble on, partly because the written part was a mix of twice repeated info and new info",{"type":378,"tag":462,"props":1525,"children":1527},{"id":1526},"custom-hooks",[1528],{"type":390,"value":889},{"type":378,"tag":379,"props":1530,"children":1531},{},[1532,1534,1539],{"type":390,"value":1533},"Magically named function starting with ",{"type":378,"tag":533,"props":1535,"children":1537},{"className":1536},[],[1538],{"type":390,"value":880},{"type":390,"value":1540},". Only hooks and components can call hooks.",{"type":378,"tag":637,"props":1542,"children":1543},{},[1544],{"type":378,"tag":1545,"props":1546,"children":1547},"ol",{},[1548,1575],{"type":378,"tag":1549,"props":1550,"children":1551},"li",{},[1552,1557,1559,1565,1567,1573],{"type":378,"tag":655,"props":1553,"children":1554},{},[1555],{"type":390,"value":1556},"React component names must start with a capital letter,",{"type":390,"value":1558}," like ",{"type":378,"tag":533,"props":1560,"children":1562},{"className":1561},[],[1563],{"type":390,"value":1564},"StatusBar",{"type":390,"value":1566}," and ",{"type":378,"tag":533,"props":1568,"children":1570},{"className":1569},[],[1571],{"type":390,"value":1572},"SaveButton",{"type":390,"value":1574},". React components also need to return something that React knows how to display, like a piece of JSX.",{"type":378,"tag":1549,"props":1576,"children":1577},{},[1578,1590,1591,1601,1603,1609],{"type":378,"tag":655,"props":1579,"children":1580},{},[1581,1583,1588],{"type":390,"value":1582},"Hook names must start with ",{"type":378,"tag":533,"props":1584,"children":1586},{"className":1585},[],[1587],{"type":390,"value":880},{"type":390,"value":1589}," followed by a capital letter,",{"type":390,"value":1558},{"type":378,"tag":383,"props":1592,"children":1595},{"href":1593,"rel":1594},"https://react.dev/reference/react/useState",[387],[1596],{"type":378,"tag":533,"props":1597,"children":1599},{"className":1598},[],[1600],{"type":390,"value":599},{"type":390,"value":1602}," (built-in) or ",{"type":378,"tag":533,"props":1604,"children":1606},{"className":1605},[],[1607],{"type":390,"value":1608},"useOnlineStatus",{"type":390,"value":1610}," (custom, like earlier on the page). Hooks may return arbitrary values.",{"title":369,"searchDepth":481,"depth":481,"links":1612},[1613,1614,1615,1616],{"id":510,"depth":484,"text":513},{"id":1399,"depth":484,"text":1406},{"id":1488,"depth":484,"text":1491},{"id":1526,"depth":484,"text":889},"content:notes-to-self:react.md","notes-to-self/react.md","notes-to-self/react",{"_path":1621,"_dir":367,"_draft":368,"_partial":368,"_locale":369,"title":275,"description":1622,"slug":276,"date":1623,"dateString":1624,"encrypted":368,"encryptedBody":373,"body":1625,"_type":485,"_id":1650,"_source":487,"_file":1651,"_stem":1652,"_extension":490},"/notes-to-self/shared-element-transition","https://github.com/WebKit/standards-positions/issues/48https://www.w3.org/2022/09/14-pagetransitions-minutes.htmlhttps://lists.w3.org/Archives/Public/www-archive/2022Sep/att-0007/set-slides.pdf",1680796800000,"2023.04.06",{"type":375,"children":1626,"toc":1648},[1627],{"type":378,"tag":379,"props":1628,"children":1629},{},[1630,1636,1642],{"type":378,"tag":383,"props":1631,"children":1634},{"href":1632,"rel":1633},"https://github.com/WebKit/standards-positions/issues/48",[387],[1635],{"type":390,"value":1632},{"type":378,"tag":383,"props":1637,"children":1640},{"href":1638,"rel":1639},"https://www.w3.org/2022/09/14-pagetransitions-minutes.html",[387],[1641],{"type":390,"value":1638},{"type":378,"tag":383,"props":1643,"children":1646},{"href":1644,"rel":1645},"https://lists.w3.org/Archives/Public/www-archive/2022Sep/att-0007/set-slides.pdf",[387],[1647],{"type":390,"value":1644},{"title":369,"searchDepth":481,"depth":481,"links":1649},[],"content:notes-to-self:shared-element-transition.md","notes-to-self/shared-element-transition.md","notes-to-self/shared-element-transition",{"_path":1654,"_dir":367,"_draft":368,"_partial":368,"_locale":369,"title":167,"description":1655,"slug":168,"date":1656,"dateString":1657,"encrypted":368,"encryptedBody":373,"body":1658,"_type":485,"_id":2543,"_source":487,"_file":2544,"_stem":2545,"_extension":490},"/notes-to-self/making-a-vuenuxt-module","You wanted your work on \"shared element transitions\" to be re-usable by others, so you began work on creating a Nuxt module using their \"starter template.\"",1680624000000,"2023.04.04",{"type":375,"children":1659,"toc":2540},[1660,1674,1685,1690,1695,1714,1727,1732,1746,1758,1763,1768,1777,1782,1791,1796,1805,1810,1823,1829,1834,1839,1848,1861,1873,1878,1934,1939,2043,2048,2053,2092,2299,2305,2310,2323,2332,2341,2350,2360,2366,2378,2391,2400,2413,2422,2435,2446,2466,2510,2521],{"type":378,"tag":379,"props":1661,"children":1662},{},[1663,1665,1672],{"type":390,"value":1664},"You wanted your work on \"shared element transitions\" to be re-usable by others, so you began work on creating a ",{"type":378,"tag":383,"props":1666,"children":1669},{"href":1667,"rel":1668},"https://nuxt.com/docs/guide/going-further/modules",[387],[1670],{"type":390,"value":1671},"Nuxt module",{"type":390,"value":1673}," using their \"starter template.\"",{"type":378,"tag":525,"props":1675,"children":1680},{"className":1676,"code":1678,"language":1679,"meta":369},[1677],"language-shell","npx nuxi init -t module vue-shared-element-transition\n","shell",[1681],{"type":378,"tag":533,"props":1682,"children":1683},{"__ignoreMap":369},[1684],{"type":390,"value":1678},{"type":378,"tag":379,"props":1686,"children":1687},{},[1688],{"type":390,"value":1689},"This includes a \"playground\" where an example app lives to test your module.",{"type":378,"tag":379,"props":1691,"children":1692},{},[1693],{"type":390,"value":1694},"The pieces for shared element transitions were:",{"type":378,"tag":1696,"props":1697,"children":1698},"ul",{},[1699,1704,1709],{"type":378,"tag":1549,"props":1700,"children":1701},{},[1702],{"type":390,"value":1703},"a composable that does most of the work",{"type":378,"tag":1549,"props":1705,"children":1706},{},[1707],{"type":390,"value":1708},"a plugin to handle scroll behavior",{"type":378,"tag":1549,"props":1710,"children":1711},{},[1712],{"type":390,"value":1713},"a component for cases where your which to apply the transition to something other than a page",{"type":378,"tag":379,"props":1715,"children":1716},{},[1717,1719,1725],{"type":390,"value":1718},"This seemed to work well. Notably, the weird callback stuff (diagrammed here: [[Shared Element Transitions + Scroll Behavior.canvas]]) was not necessary as, in the playground, the ",{"type":378,"tag":533,"props":1720,"children":1722},{"className":1721},[],[1723],{"type":390,"value":1724},"scrollBehavior",{"type":390,"value":1726}," hook was being called at the \"right\" time.",{"type":378,"tag":379,"props":1728,"children":1729},{},[1730],{"type":390,"value":1731},"But you realized that there might be utility for a plain Vue module, not just Nuxt. Your Nuxt module would then use this Vue module.",{"type":378,"tag":379,"props":1733,"children":1734},{},[1735,1737,1744],{"type":390,"value":1736},"There doesn't seem to be an official starter template for Vue modules. You basically took ",{"type":378,"tag":383,"props":1738,"children":1741},{"href":1739,"rel":1740},"https://github.com/hexagon06/vue3-boilerplate-library",[387],[1742],{"type":390,"value":1743},"this \"boilerplate\"",{"type":390,"value":1745}," and removed the storybook parts (storybook is a big tool for documenting/testing components).",{"type":378,"tag":379,"props":1747,"children":1748},{},[1749,1756],{"type":378,"tag":383,"props":1750,"children":1753},{"href":1751,"rel":1752},"https://github.com/wuruoyun/vue-component-lib-starter",[387],[1754],{"type":390,"value":1755},"This template looks lighter",{"type":390,"value":1757}," but you have not tried it yet.",{"type":378,"tag":379,"props":1759,"children":1760},{},[1761],{"type":390,"value":1762},"You got the Vue module working. The problem came when you wanted to test it in the Nuxt module.",{"type":378,"tag":379,"props":1764,"children":1765},{},[1766],{"type":390,"value":1767},"The correct way to do this is with npm/yarn link. You go to the directory of the project you want to link to, in this case the Vue module. You type",{"type":378,"tag":525,"props":1769,"children":1772},{"className":1770,"code":1771,"language":1679,"meta":369},[1677],"~/vue-shared-element-transition/ > yarn link\n",[1773],{"type":378,"tag":533,"props":1774,"children":1775},{"__ignoreMap":369},[1776],{"type":390,"value":1771},{"type":378,"tag":379,"props":1778,"children":1779},{},[1780],{"type":390,"value":1781},"Then you to the directory you want to link from, as if the module were installed:",{"type":378,"tag":525,"props":1783,"children":1786},{"className":1784,"code":1785,"language":1679,"meta":369},[1677],"~/nuxt-shared-element-transition/ > yarn link vue-shared-element-transition\n",[1787],{"type":378,"tag":533,"props":1788,"children":1789},{"__ignoreMap":369},[1790],{"type":390,"value":1785},{"type":378,"tag":379,"props":1792,"children":1793},{},[1794],{"type":390,"value":1795},"However, when attempting to import in the code, e.g.",{"type":378,"tag":525,"props":1797,"children":1800},{"className":1798,"code":1799,"language":530,"meta":369},[528],"import { useSharedElementTransition } from \"vue-shared-element-transition\";\n",[1801],{"type":378,"tag":533,"props":1802,"children":1803},{"__ignoreMap":369},[1804],{"type":390,"value":1799},{"type":378,"tag":379,"props":1806,"children":1807},{},[1808],{"type":390,"value":1809},"You could get an error message stating that the file couldn't be found, even though it correctly listed the actual path to the file it was looking for.",{"type":378,"tag":379,"props":1811,"children":1812},{},[1813,1815,1821],{"type":390,"value":1814},"There is some ",{"type":378,"tag":533,"props":1816,"children":1818},{"className":1817},[],[1819],{"type":390,"value":1820},"symLink",{"type":390,"value":1822}," setting for some part of vite, but it doesn't appear to be exposed in Nuxt? You are about to attempt to simplify the problem by creating a new Vue component and a new Nuxt component to more destructively experiment with.",{"type":378,"tag":625,"props":1824,"children":1826},{"id":1825},"update",[1827],{"type":390,"value":1828},"Update",{"type":378,"tag":379,"props":1830,"children":1831},{},[1832],{"type":390,"value":1833},"When you attempted to recreate with bare projects, you saw additional info pointing you to the following solution.",{"type":378,"tag":379,"props":1835,"children":1836},{},[1837],{"type":390,"value":1838},"For the link to work, you need to add the following to nuxt config:",{"type":378,"tag":525,"props":1840,"children":1843},{"className":1841,"code":1842,"language":530,"meta":369},[528],"import { searchForWorkspaceRoot } from 'vite'\n\nexport default defineNuxtConfig({\n  // ...\n  vite: {\n    server: {\n      fs: {\n        strict: false\n      }\n    }\n  }\n})\n",[1844],{"type":378,"tag":533,"props":1845,"children":1846},{"__ignoreMap":369},[1847],{"type":390,"value":1842},{"type":378,"tag":379,"props":1849,"children":1850},{},[1851,1853,1859],{"type":390,"value":1852},"You did this in playground's ",{"type":378,"tag":533,"props":1854,"children":1856},{"className":1855},[],[1857],{"type":390,"value":1858},"nuxt.config.ts",{"type":390,"value":1860}," -- doesn't seem necessary to build though? Not sure.",{"type":378,"tag":637,"props":1862,"children":1863},{},[1864],{"type":378,"tag":379,"props":1865,"children":1866},{},[1867,1871],{"type":378,"tag":644,"props":1868,"children":1869},{},[1870],{"type":390,"value":1200},{"type":390,"value":1872},"\nMay be obvious, but you will need to release the vue component in order to release the nuxt component. Don't forget.",{"type":378,"tag":379,"props":1874,"children":1875},{},[1876],{"type":390,"value":1877},"** CONFIRMED ** The above worked as a fix for the real modules I was working on. Next steps:",{"type":378,"tag":1545,"props":1879,"children":1880},{},[1881,1886,1891,1904,1909,1914,1919,1924,1929],{"type":378,"tag":1549,"props":1882,"children":1883},{},[1884],{"type":390,"value":1885},"Review packages and methods from both \"boilerplate\" vue projects you used. VitePress used on the second project seems like a useful addition, as it can be used to both test, demonstrate, and document the component.",{"type":378,"tag":1549,"props":1887,"children":1888},{},[1889],{"type":390,"value":1890},"Once settled:",{"type":378,"tag":1549,"props":1892,"children":1893},{},[1894,1896,1902],{"type":390,"value":1895},"Init git; ensure that ",{"type":378,"tag":533,"props":1897,"children":1899},{"className":1898},[],[1900],{"type":390,"value":1901},".gitinit",{"type":390,"value":1903}," is properly configured",{"type":378,"tag":1549,"props":1905,"children":1906},{},[1907],{"type":390,"value":1908},"Ensure that linting/prettying is in place",{"type":378,"tag":1549,"props":1910,"children":1911},{},[1912],{"type":390,"value":1913},"Create demonstration in Vue project (like demo in Nuxt project)",{"type":378,"tag":1549,"props":1915,"children":1916},{},[1917],{"type":390,"value":1918},"Create documentation in Vue Project",{"type":378,"tag":1549,"props":1920,"children":1921},{},[1922],{"type":390,"value":1923},"Explore vue router - that's where scrollPosition comes in, so maybe should not be limited to nuxt",{"type":378,"tag":1549,"props":1925,"children":1926},{},[1927],{"type":390,"value":1928},"Publish",{"type":378,"tag":1549,"props":1930,"children":1931},{},[1932],{"type":390,"value":1933},"Repeat for nuxt project",{"type":378,"tag":379,"props":1935,"children":1936},{},[1937],{"type":390,"value":1938},"Unanticipated additional work:",{"type":378,"tag":1696,"props":1940,"children":1941},{},[1942,2000],{"type":378,"tag":1549,"props":1943,"children":1944},{},[1945,1947,1953,1955,1961,1963,1969,1971,1977,1979,1984,1986,1991,1993,1998],{"type":390,"value":1946},"The way to have a module that automatically can be used universally in vue is to have its main library export a default with an import function, e.g. ",{"type":378,"tag":533,"props":1948,"children":1950},{"className":1949},[],[1951],{"type":390,"value":1952},"return default { import(app:Vue): {} }",{"type":390,"value":1954},". There are two kinds of modules you may need to support, ",{"type":378,"tag":533,"props":1956,"children":1958},{"className":1957},[],[1959],{"type":390,"value":1960},"umd",{"type":390,"value":1962}," and ",{"type":378,"tag":533,"props":1964,"children":1966},{"className":1965},[],[1967],{"type":390,"value":1968},"esm",{"type":390,"value":1970}," (which I guess just uses an ",{"type":378,"tag":533,"props":1972,"children":1974},{"className":1973},[],[1975],{"type":390,"value":1976},"es",{"type":390,"value":1978}," file extension) but ",{"type":378,"tag":533,"props":1980,"children":1982},{"className":1981},[],[1983],{"type":390,"value":1960},{"type":390,"value":1985}," is weird about exporting both default (for magic) and named exports. You are going to ignore ",{"type":378,"tag":533,"props":1987,"children":1989},{"className":1988},[],[1990],{"type":390,"value":1960},{"type":390,"value":1992}," for now. EDIT: no you figured it out, kind of. vue3-boilerplate had es and umd flipped (umd is for require, es cannot be required). Can't export default, so have to export named import function, then import that import function and ",{"type":378,"tag":533,"props":1994,"children":1996},{"className":1995},[],[1997],{"type":390,"value":880},{"type":390,"value":1999}," it from plugin.",{"type":378,"tag":1549,"props":2001,"children":2002},{},[2003,2005,2011,2013,2019,2021,2026,2028,2034,2036,2041],{"type":390,"value":2004},"There is some issue with a lynchpin in your shared-element-transition: you are using \"globals\" in the ",{"type":378,"tag":533,"props":2006,"children":2008},{"className":2007},[],[2009],{"type":390,"value":2010},"sharedElementTransition",{"type":390,"value":2012}," composable to store data about the elements that are transitioning. Not good for es module as it seems one of the globals is reduced to ",{"type":378,"tag":533,"props":2014,"children":2016},{"className":2015},[],[2017],{"type":390,"value":2018},"h",{"type":390,"value":2020},", and a different ",{"type":378,"tag":533,"props":2022,"children":2024},{"className":2023},[],[2025],{"type":390,"value":2018},{"type":390,"value":2027}," is prepended. The composable is called from different components -- how else to share that info (both from and to elements must be considered). Note: you refactored consts into single object ",{"type":378,"tag":533,"props":2029,"children":2031},{"className":2030},[],[2032],{"type":390,"value":2033},"g",{"type":390,"value":2035}," and the different ",{"type":378,"tag":533,"props":2037,"children":2039},{"className":2038},[],[2040],{"type":390,"value":2018},{"type":390,"value":2042}," import that was prepended magically went away. WTF.",{"type":378,"tag":379,"props":2044,"children":2045},{},[2046],{"type":390,"value":2047},"Unexpected complexity:",{"type":378,"tag":379,"props":2049,"children":2050},{},[2051],{"type":390,"value":2052},"You need to have",{"type":378,"tag":1696,"props":2054,"children":2055},{},[2056,2069,2074,2087],{"type":378,"tag":1549,"props":2057,"children":2058},{},[2059,2061],{"type":390,"value":2060},"Vue Project with\n",{"type":378,"tag":1696,"props":2062,"children":2063},{},[2064],{"type":378,"tag":1549,"props":2065,"children":2066},{},[2067],{"type":390,"value":2068},"VitePress site built in to test and doc",{"type":378,"tag":1549,"props":2070,"children":2071},{},[2072],{"type":390,"value":2073},"External example vue project to test import",{"type":378,"tag":1549,"props":2075,"children":2076},{},[2077,2079],{"type":390,"value":2078},"Nuxt Project with\n",{"type":378,"tag":1696,"props":2080,"children":2081},{},[2082],{"type":378,"tag":1549,"props":2083,"children":2084},{},[2085],{"type":390,"value":2086},"Nuxt playground site built in",{"type":378,"tag":1549,"props":2088,"children":2089},{},[2090],{"type":390,"value":2091},"External example nuxt project to test import",{"type":378,"tag":2093,"props":2094,"children":2095},"table",{},[2096,2125],{"type":378,"tag":2097,"props":2098,"children":2099},"thead",{},[2100],{"type":378,"tag":2101,"props":2102,"children":2103},"tr",{},[2104,2110,2115,2120],{"type":378,"tag":2105,"props":2106,"children":2107},"th",{},[2108],{"type":390,"value":2109},"Property",{"type":378,"tag":2105,"props":2111,"children":2112},{},[2113],{"type":390,"value":2114},"Type",{"type":378,"tag":2105,"props":2116,"children":2117},{},[2118],{"type":390,"value":2119},"Default",{"type":378,"tag":2105,"props":2121,"children":2122},{},[2123],{"type":390,"value":2124},"Description",{"type":378,"tag":2126,"props":2127,"children":2128},"tbody",{},[2129,2165,2200,2231,2265],{"type":378,"tag":2101,"props":2130,"children":2131},{},[2132,2142,2151,2160],{"type":378,"tag":2133,"props":2134,"children":2135},"td",{},[2136],{"type":378,"tag":533,"props":2137,"children":2139},{"className":2138},[],[2140],{"type":390,"value":2141},"speed",{"type":378,"tag":2133,"props":2143,"children":2144},{},[2145],{"type":378,"tag":533,"props":2146,"children":2148},{"className":2147},[],[2149],{"type":390,"value":2150},"number",{"type":378,"tag":2133,"props":2152,"children":2153},{},[2154],{"type":378,"tag":533,"props":2155,"children":2157},{"className":2156},[],[2158],{"type":390,"value":2159},"222",{"type":378,"tag":2133,"props":2161,"children":2162},{},[2163],{"type":390,"value":2164},"Speed of the transition, in milliseconds",{"type":378,"tag":2101,"props":2166,"children":2167},{},[2168,2177,2186,2195],{"type":378,"tag":2133,"props":2169,"children":2170},{},[2171],{"type":378,"tag":533,"props":2172,"children":2174},{"className":2173},[],[2175],{"type":390,"value":2176},"group",{"type":378,"tag":2133,"props":2178,"children":2179},{},[2180],{"type":378,"tag":533,"props":2181,"children":2183},{"className":2182},[],[2184],{"type":390,"value":2185},"string",{"type":378,"tag":2133,"props":2187,"children":2188},{},[2189],{"type":378,"tag":533,"props":2190,"children":2192},{"className":2191},[],[2193],{"type":390,"value":2194},"\"page\"",{"type":378,"tag":2133,"props":2196,"children":2197},{},[2198],{"type":390,"value":2199},"An arbitrary group name to associate transitions with. Transitions belonging to different groups will not affect each other.",{"type":378,"tag":2101,"props":2201,"children":2202},{},[2203,2212,2221,2226],{"type":378,"tag":2133,"props":2204,"children":2205},{},[2206],{"type":378,"tag":533,"props":2207,"children":2209},{"className":2208},[],[2210],{"type":390,"value":2211},"comparator",{"type":378,"tag":2133,"props":2213,"children":2214},{},[2215],{"type":378,"tag":533,"props":2216,"children":2218},{"className":2217},[],[2219],{"type":390,"value":2220},"ComparatorType",{"type":378,"tag":2133,"props":2222,"children":2223},{},[2224],{"type":390,"value":2225},"* see note",{"type":378,"tag":2133,"props":2227,"children":2228},{},[2229],{"type":390,"value":2230},"A comparator function to use when considering Relative Slide transitions.",{"type":378,"tag":2101,"props":2232,"children":2233},{},[2234,2243,2251,2260],{"type":378,"tag":2133,"props":2235,"children":2236},{},[2237],{"type":378,"tag":533,"props":2238,"children":2240},{"className":2239},[],[2241],{"type":390,"value":2242},"x",{"type":378,"tag":2133,"props":2244,"children":2245},{},[2246],{"type":378,"tag":533,"props":2247,"children":2249},{"className":2248},[],[2250],{"type":390,"value":2185},{"type":378,"tag":2133,"props":2252,"children":2253},{},[2254],{"type":378,"tag":533,"props":2255,"children":2257},{"className":2256},[],[2258],{"type":390,"value":2259},"\"25%\"",{"type":378,"tag":2133,"props":2261,"children":2262},{},[2263],{"type":390,"value":2264},"A valid CSS unit value for horizontal animation on Relative Slide transitions.",{"type":378,"tag":2101,"props":2266,"children":2267},{},[2268,2277,2285,2294],{"type":378,"tag":2133,"props":2269,"children":2270},{},[2271],{"type":378,"tag":533,"props":2272,"children":2274},{"className":2273},[],[2275],{"type":390,"value":2276},"y",{"type":378,"tag":2133,"props":2278,"children":2279},{},[2280],{"type":378,"tag":533,"props":2281,"children":2283},{"className":2282},[],[2284],{"type":390,"value":2185},{"type":378,"tag":2133,"props":2286,"children":2287},{},[2288],{"type":378,"tag":533,"props":2289,"children":2291},{"className":2290},[],[2292],{"type":390,"value":2293},"\"0%\"",{"type":378,"tag":2133,"props":2295,"children":2296},{},[2297],{"type":390,"value":2298},"A valid CSS unit value for vertical animation on Relative Slide transitions.",{"type":378,"tag":462,"props":2300,"children":2302},{"id":2301},"getting-info-from-nuxt-config-into-front-endclient",[2303],{"type":390,"value":2304},"Getting Info from Nuxt Config into Front End/Client",{"type":378,"tag":379,"props":2306,"children":2307},{},[2308],{"type":390,"value":2309},"Nuxt module doesn’t run client side. It’s config info only exists at build time/server side.",{"type":378,"tag":379,"props":2311,"children":2312},{},[2313,2315,2321],{"type":390,"value":2314},"To pass info, you have to modify the ",{"type":378,"tag":533,"props":2316,"children":2318},{"className":2317},[],[2319],{"type":390,"value":2320},"runtimeConfig",{"type":390,"value":2322}," object available on the nuxt instance the module receives.",{"type":378,"tag":525,"props":2324,"children":2327},{"className":2325,"code":2326,"language":530,"meta":369},[528],"// your module.ts\n\nimport { defineNuxtModule, addPlugin, addImports, createResolver } from '@nuxt/kit'\n\n// Module options TypeScript interface definition\n// this configurable in nuxt.config.ts\nexport interface ModuleOptions {\n  foo: string;\n}\n\nexport default defineNuxtModule\u003CModuleOptions>({\n  meta: {\n    name: 'your-module',\n    configKey: 'yourModule',\n    compatibility: {\n      nuxt: '^3.0.0'\n    }\n  },\n  \n  // Default configuration options of the Nuxt module\n  defaults: { foo: 'bar' },\n  \n  setup (options, nuxt) {\n    nuxt.options.runtimeConfig.public.yourModule = {\n      foo: options.foo\n    };\n    // etc. \n  }\n})\n",[2328],{"type":378,"tag":533,"props":2329,"children":2330},{"__ignoreMap":369},[2331],{"type":390,"value":2326},{"type":378,"tag":525,"props":2333,"children":2336},{"className":2334,"code":2335,"language":530,"meta":369},[528],"// nuxt.config.ts\nexport default defineNuxtConfig({\n  yourModule: {\n    foo: 'baz'\n  }\n})\n",[2337],{"type":378,"tag":533,"props":2338,"children":2339},{"__ignoreMap":369},[2340],{"type":390,"value":2335},{"type":378,"tag":525,"props":2342,"children":2345},{"className":2343,"code":2344,"language":530,"meta":369},[528],"// then, elsewhere\nimport { useRuntimeConfig } from '#app'\nconsole.log(runtimeConfig.public.yourModule.foo)\n// -> \"baz\" b/c that's what's in defineNuxtConfig\n",[2346],{"type":378,"tag":533,"props":2347,"children":2348},{"__ignoreMap":369},[2349],{"type":390,"value":2344},{"type":378,"tag":379,"props":2351,"children":2352},{},[2353],{"type":378,"tag":383,"props":2354,"children":2357},{"href":2355,"rel":2356},"https://devpress.csdn.net/vue/62f41b03c6770329307f9562.html",[387],[2358],{"type":390,"value":2359},"this was helpful but outdated",{"type":378,"tag":625,"props":2361,"children":2363},{"id":2362},"problem",[2364],{"type":390,"value":2365},"PROBLEM",{"type":378,"tag":379,"props":2367,"children":2368},{},[2369,2371,2376],{"type":390,"value":2370},"Can’t build because the ",{"type":378,"tag":655,"props":2372,"children":2373},{},[2374],{"type":390,"value":2375},"playground",{"type":390,"value":2377}," gets in inferred type for the runtime config.",{"type":378,"tag":379,"props":2379,"children":2380},{},[2381,2383,2389],{"type":390,"value":2382},"In generated ",{"type":378,"tag":533,"props":2384,"children":2386},{"className":2385},[],[2387],{"type":390,"value":2388},"playground/.nuxt/types/schema.d.ts",{"type":390,"value":2390}," we get",{"type":378,"tag":525,"props":2392,"children":2395},{"className":2393,"code":2394,"language":530,"meta":369},[528],"import { NuxtModule, RuntimeConfig } from 'nuxt/schema'\ndeclare module 'nuxt/schema' {\n  // ...\n  interface PublicRuntimeConfig {\n   contextualTransition: {\n      hashScroll: boolean, // or string depending on how its set\n   },\n  }\n}\n\n",[2396],{"type":378,"tag":533,"props":2397,"children":2398},{"__ignoreMap":369},[2399],{"type":390,"value":2394},{"type":378,"tag":379,"props":2401,"children":2402},{},[2403,2405,2411],{"type":390,"value":2404},"where we declare the module options in ",{"type":378,"tag":533,"props":2406,"children":2408},{"className":2407},[],[2409],{"type":390,"value":2410},"module.ts",{"type":390,"value":2412}," as",{"type":378,"tag":525,"props":2414,"children":2417},{"className":2415,"code":2416,"language":530,"meta":369},[528],"export type HashScrollType = false | 'smooth' | 'instant' | 'auto';\n\nexport interface ModuleOptions {\n  hashScroll: HashScrollType;\n}\n",[2418],{"type":378,"tag":533,"props":2419,"children":2420},{"__ignoreMap":369},[2421],{"type":390,"value":2416},{"type":378,"tag":379,"props":2423,"children":2424},{},[2425,2427,2433],{"type":390,"value":2426},"The playground works, but ",{"type":378,"tag":533,"props":2428,"children":2430},{"className":2429},[],[2431],{"type":390,"value":2432},"yarn prepack",{"type":390,"value":2434}," fails because:",{"type":378,"tag":525,"props":2436,"children":2441},{"className":2437,"code":2439,"language":2440,"meta":369},[2438],"language-sh","src/module.ts(34,5): error TS2322: Type '{ hashScroll: HashScrollType; }' is not assignable to type '{ hashScroll: boolean; }'.\n  Types of property 'hashScroll' are incompatible.\n    Type 'string | boolean' is not assignable to type 'boolean'.\n      Type 'string' is not assignable to type 'boolean'.\n","sh",[2442],{"type":378,"tag":533,"props":2443,"children":2444},{"__ignoreMap":369},[2445],{"type":390,"value":2439},{"type":378,"tag":379,"props":2447,"children":2448},{},[2449,2456,2458,2464],{"type":378,"tag":383,"props":2450,"children":2453},{"href":2451,"rel":2452},"https://nuxt.com/docs/guide/going-further/runtime-config#manually-typing-runtime-config",[387],[2454],{"type":390,"value":2455},"This states",{"type":390,"value":2457}," that you can manually set the runtime config. But where does ",{"type":378,"tag":533,"props":2459,"children":2461},{"className":2460},[],[2462],{"type":390,"value":2463},"index.d.ts",{"type":390,"value":2465}," get used?",{"type":378,"tag":379,"props":2467,"children":2468},{},[2469,2471,2477,2479,2485,2487,2492,2494,2500,2502,2508],{"type":390,"value":2470},"There is a weird reliance on the generated ",{"type":378,"tag":533,"props":2472,"children":2474},{"className":2473},[],[2475],{"type":390,"value":2476},"playground/.nuxt/tsconfig.json",{"type":390,"value":2478}," file from the main module directory’s ",{"type":378,"tag":533,"props":2480,"children":2482},{"className":2481},[],[2483],{"type":390,"value":2484},"tsconfig.json",{"type":390,"value":2486}," file. I discovered by manually including a directory with my ",{"type":378,"tag":533,"props":2488,"children":2490},{"className":2489},[],[2491],{"type":390,"value":2463},{"type":390,"value":2493}," in it (",{"type":378,"tag":533,"props":2495,"children":2497},{"className":2496},[],[2498],{"type":390,"value":2499},"types/index.d.ts",{"type":390,"value":2501},"), along with the paths originally automatically set in the playground’s version, I could get both the playground and the ",{"type":378,"tag":533,"props":2503,"children":2505},{"className":2504},[],[2506],{"type":390,"value":2507},"prepack",{"type":390,"value":2509}," command to work:",{"type":378,"tag":525,"props":2511,"children":2516},{"className":2512,"code":2514,"language":2515,"meta":369},[2513],"language-json","{\n  \"extends\": \"./playground/.nuxt/tsconfig.json\",\n  \"include\": [\n      \"./types\",\n      \"./playground/.nuxt/nuxt.d.ts\",\n      \"./playground/**/*\",\n      \"./**/*\"\n  ]\n}\n","json",[2517],{"type":378,"tag":533,"props":2518,"children":2519},{"__ignoreMap":369},[2520],{"type":390,"value":2514},{"type":378,"tag":379,"props":2522,"children":2523},{},[2524,2526,2531,2533,2538],{"type":390,"value":2525},"But without that, I couldn’t figure out where to put the ",{"type":378,"tag":533,"props":2527,"children":2529},{"className":2528},[],[2530],{"type":390,"value":2463},{"type":390,"value":2532}," so that it would get picked up, or, at least, so that ",{"type":378,"tag":533,"props":2534,"children":2536},{"className":2535},[],[2537],{"type":390,"value":2432},{"type":390,"value":2539}," would work.",{"title":369,"searchDepth":481,"depth":481,"links":2541},[2542],{"id":2301,"depth":484,"text":2304},"content:notes-to-self:making-a-vuenuxt-module.md","notes-to-self/making-a-vuenuxt-module.md","notes-to-self/making-a-vuenuxt-module",{"_path":2547,"_dir":367,"_draft":368,"_partial":368,"_locale":369,"title":251,"description":2548,"slug":252,"date":2549,"dateString":2550,"encrypted":368,"encryptedBody":373,"body":2551,"_type":485,"_id":2868,"_source":487,"_file":2869,"_stem":2870,"_extension":490},"/notes-to-self/red-sea-navigation","Components:",1679068800000,"2023.03.17",{"type":375,"children":2552,"toc":2866},[2553,2557,2575,2580,2679,2684,2746,2751,2861],{"type":378,"tag":379,"props":2554,"children":2555},{},[2556],{"type":390,"value":2548},{"type":378,"tag":1696,"props":2558,"children":2559},{},[2560,2565,2570],{"type":378,"tag":1549,"props":2561,"children":2562},{},[2563],{"type":390,"value":2564},"Brand/Logo/Home",{"type":378,"tag":1549,"props":2566,"children":2567},{},[2568],{"type":390,"value":2569},"Menu - horizontal on desktop, panel with stacked items on mobile",{"type":378,"tag":1549,"props":2571,"children":2572},{},[2573],{"type":390,"value":2574},"Toggle Button - open and close the menu when it may be hidden",{"type":378,"tag":379,"props":2576,"children":2577},{},[2578],{"type":390,"value":2579},"Each has three independent states. Only common state is \"hidden\"",{"type":378,"tag":2093,"props":2581,"children":2582},{},[2583,2609],{"type":378,"tag":2097,"props":2584,"children":2585},{},[2586],{"type":378,"tag":2101,"props":2587,"children":2588},{},[2589,2594,2599,2604],{"type":378,"tag":2105,"props":2590,"children":2591},{},[2592],{"type":390,"value":2593},"Component",{"type":378,"tag":2105,"props":2595,"children":2596},{},[2597],{"type":390,"value":2598},"A State",{"type":378,"tag":2105,"props":2600,"children":2601},{},[2602],{"type":390,"value":2603},"B State",{"type":378,"tag":2105,"props":2605,"children":2606},{},[2607],{"type":390,"value":2608},"C State",{"type":378,"tag":2126,"props":2610,"children":2611},{},[2612,2635,2657],{"type":378,"tag":2101,"props":2613,"children":2614},{},[2615,2620,2625,2630],{"type":378,"tag":2133,"props":2616,"children":2617},{},[2618],{"type":390,"value":2619},"1. Brand/Logo/Home",{"type":378,"tag":2133,"props":2621,"children":2622},{},[2623],{"type":390,"value":2624},"Full",{"type":378,"tag":2133,"props":2626,"children":2627},{},[2628],{"type":390,"value":2629},"Collapsed",{"type":378,"tag":2133,"props":2631,"children":2632},{},[2633],{"type":390,"value":2634},"Hidden",{"type":378,"tag":2101,"props":2636,"children":2637},{},[2638,2643,2648,2653],{"type":378,"tag":2133,"props":2639,"children":2640},{},[2641],{"type":390,"value":2642},"2. Menu",{"type":378,"tag":2133,"props":2644,"children":2645},{},[2646],{"type":390,"value":2647},"Horizontal Bar",{"type":378,"tag":2133,"props":2649,"children":2650},{},[2651],{"type":390,"value":2652},"Stacked Panel",{"type":378,"tag":2133,"props":2654,"children":2655},{},[2656],{"type":390,"value":2634},{"type":378,"tag":2101,"props":2658,"children":2659},{},[2660,2665,2670,2675],{"type":378,"tag":2133,"props":2661,"children":2662},{},[2663],{"type":390,"value":2664},"3. Toggle Button",{"type":378,"tag":2133,"props":2666,"children":2667},{},[2668],{"type":390,"value":2669},"Open/Hamburger",{"type":378,"tag":2133,"props":2671,"children":2672},{},[2673],{"type":390,"value":2674},"Close/X",{"type":378,"tag":2133,"props":2676,"children":2677},{},[2678],{"type":390,"value":2634},{"type":378,"tag":379,"props":2680,"children":2681},{},[2682],{"type":390,"value":2683},"The page has multiple states",{"type":378,"tag":1696,"props":2685,"children":2686},{},[2687,2728],{"type":378,"tag":1549,"props":2688,"children":2689},{},[2690,2692],{"type":390,"value":2691},"Detectable by classes applied via JavaScript\n",{"type":378,"tag":1696,"props":2693,"children":2694},{},[2695,2700,2718,2723],{"type":378,"tag":1549,"props":2696,"children":2697},{},[2698],{"type":390,"value":2699},"Unscrolled - scrolled to the very top of the page beneath some threshold",{"type":378,"tag":1549,"props":2701,"children":2702},{},[2703,2705],{"type":390,"value":2704},"Scrolled - scrolled down, beyond some threshold\n",{"type":378,"tag":1696,"props":2706,"children":2707},{},[2708,2713],{"type":378,"tag":1549,"props":2709,"children":2710},{},[2711],{"type":390,"value":2712},"Scrolled up - most recently scrolled up toward the top",{"type":378,"tag":1549,"props":2714,"children":2715},{},[2716],{"type":390,"value":2717},"Scroll down - most recently scrolled down toward the bottom",{"type":378,"tag":1549,"props":2719,"children":2720},{},[2721],{"type":390,"value":2722},"Untoggled - the toggle button has not been clicked (or has been toggled off)",{"type":378,"tag":1549,"props":2724,"children":2725},{},[2726],{"type":390,"value":2727},"Toggled - the toggle button has been clicked",{"type":378,"tag":1549,"props":2729,"children":2730},{},[2731,2733],{"type":390,"value":2732},"Detectable by media query\n",{"type":378,"tag":1696,"props":2734,"children":2735},{},[2736,2741],{"type":378,"tag":1549,"props":2737,"children":2738},{},[2739],{"type":390,"value":2740},"Desktop",{"type":378,"tag":1549,"props":2742,"children":2743},{},[2744],{"type":390,"value":2745},"Mobile",{"type":378,"tag":379,"props":2747,"children":2748},{},[2749],{"type":390,"value":2750},"The states of the components are applied according to the page’s state:",{"type":378,"tag":2093,"props":2752,"children":2753},{},[2754,2779],{"type":378,"tag":2097,"props":2755,"children":2756},{},[2757],{"type":378,"tag":2101,"props":2758,"children":2759},{},[2760,2763,2767,2771,2775],{"type":378,"tag":2105,"props":2761,"children":2762},{},[],{"type":378,"tag":2105,"props":2764,"children":2765},{},[2766],{"type":390,"value":2745},{"type":378,"tag":2105,"props":2768,"children":2769},{},[2770],{"type":390,"value":2745},{"type":378,"tag":2105,"props":2772,"children":2773},{},[2774],{"type":390,"value":2740},{"type":378,"tag":2105,"props":2776,"children":2777},{},[2778],{"type":390,"value":2740},{"type":378,"tag":2126,"props":2780,"children":2781},{},[2782,2807,2835],{"type":378,"tag":2101,"props":2783,"children":2784},{},[2785,2788,2793,2798,2803],{"type":378,"tag":2133,"props":2786,"children":2787},{},[],{"type":378,"tag":2133,"props":2789,"children":2790},{},[2791],{"type":390,"value":2792},"Unscrolled/Up",{"type":378,"tag":2133,"props":2794,"children":2795},{},[2796],{"type":390,"value":2797},"Scrolled",{"type":378,"tag":2133,"props":2799,"children":2800},{},[2801],{"type":390,"value":2802},"Unscrolled",{"type":378,"tag":2133,"props":2804,"children":2805},{},[2806],{"type":390,"value":2797},{"type":378,"tag":2101,"props":2808,"children":2809},{},[2810,2815,2820,2825,2830],{"type":378,"tag":2133,"props":2811,"children":2812},{},[2813],{"type":390,"value":2814},"Untoggled",{"type":378,"tag":2133,"props":2816,"children":2817},{},[2818],{"type":390,"value":2819},"1A, 2C, 3A",{"type":378,"tag":2133,"props":2821,"children":2822},{},[2823],{"type":390,"value":2824},"1C, 2C, 3C",{"type":378,"tag":2133,"props":2826,"children":2827},{},[2828],{"type":390,"value":2829},"1A, 2A, 3C",{"type":378,"tag":2133,"props":2831,"children":2832},{},[2833],{"type":390,"value":2834},"1B, 2C, 3A",{"type":378,"tag":2101,"props":2836,"children":2837},{},[2838,2843,2848,2852,2856],{"type":378,"tag":2133,"props":2839,"children":2840},{},[2841],{"type":390,"value":2842},"Toggled",{"type":378,"tag":2133,"props":2844,"children":2845},{},[2846],{"type":390,"value":2847},"1A, 2B, 3B",{"type":378,"tag":2133,"props":2849,"children":2850},{},[2851],{"type":390,"value":2847},{"type":378,"tag":2133,"props":2853,"children":2854},{},[2855],{"type":390,"value":2829},{"type":378,"tag":2133,"props":2857,"children":2858},{},[2859],{"type":390,"value":2860},"1A, 2A, 3B",{"type":378,"tag":379,"props":2862,"children":2863},{},[2864],{"type":390,"value":2865},"Note that for mobile, the \"unscrolled\" state applies to both unscrolled and scrolled + scrolled up, where for desktop unscrolled means only scrolled to the top.",{"title":369,"searchDepth":481,"depth":481,"links":2867},[],"content:notes-to-self:red-sea-navigation.md","notes-to-self/red-sea-navigation.md","notes-to-self/red-sea-navigation",{"_path":2872,"_dir":367,"_draft":368,"_partial":368,"_locale":369,"title":53,"description":2873,"slug":54,"date":2874,"dateString":2875,"encrypted":368,"encryptedBody":373,"body":2876,"_type":485,"_id":2903,"_source":487,"_file":2904,"_stem":2905,"_extension":490},"/notes-to-self/cms","Noting that Netlify CMS has been taken over as open source project and renamed \"Decap CMS\" MIT License Repo At the time of this writing, repo README has not been updated (still says Netlify). Looks like last commits were a year ago, thus the language about \"saving\" it. netlifycms.org is still live and lists last changes in April of 2020.",1677776400000,"2023.03.02",{"type":375,"children":2877,"toc":2901},[2878],{"type":378,"tag":379,"props":2879,"children":2880},{},[2881,2883,2890,2892,2899],{"type":390,"value":2882},"Noting that Netlify CMS has been taken over as open source project and renamed ",{"type":378,"tag":383,"props":2884,"children":2887},{"href":2885,"rel":2886},"https://decapcms.org",[387],[2888],{"type":390,"value":2889},"\"Decap CMS\"",{"type":390,"value":2891}," ",{"type":378,"tag":383,"props":2893,"children":2896},{"href":2894,"rel":2895},"https://github.com/decaporg/decap-cms",[387],[2897],{"type":390,"value":2898},"MIT License Repo",{"type":390,"value":2900}," At the time of this writing, repo README has not been updated (still says Netlify). Looks like last commits were a year ago, thus the language about \"saving\" it. netlifycms.org is still live and lists last changes in April of 2020.",{"title":369,"searchDepth":481,"depth":481,"links":2902},[],"content:notes-to-self:cms.md","notes-to-self/cms.md","notes-to-self/cms",{"_path":2907,"_dir":367,"_draft":368,"_partial":368,"_locale":369,"title":14,"description":2908,"slug":15,"date":2909,"dateString":2910,"encrypted":368,"encryptedBody":373,"body":2911,"_type":485,"_id":2948,"_source":487,"_file":2949,"_stem":2950,"_extension":490},"/notes-to-self/a-mathematical-expression-parser-in-typescript","You want to make an expression parser for BC as a presumed send-off gift to handle things like ((CASTERLEVEL/3)+1). See spells-formatted.json in Downloads. Many seem to exist?",1677085200000,"2023.02.22",{"type":375,"children":2912,"toc":2946},[2913,2926,2938],{"type":378,"tag":379,"props":2914,"children":2915},{},[2916,2918,2924],{"type":390,"value":2917},"You want to make an expression parser for BC as a presumed send-off gift to handle things like ((CASTERLEVEL/3)+1). See ",{"type":378,"tag":533,"props":2919,"children":2921},{"className":2920},[],[2922],{"type":390,"value":2923},"spells-formatted.json",{"type":390,"value":2925}," in Downloads. Many seem to exist?",{"type":378,"tag":379,"props":2927,"children":2928},{},[2929,2930,2936],{"type":390,"value":403},{"type":378,"tag":383,"props":2931,"children":2934},{"href":2932,"rel":2933},"https://blog.bitsrc.io/parsing-expressions-in-javascript-4c156f0cbaec",[387],[2935],{"type":390,"value":2932},{"type":390,"value":2937}," for inspiration. PEDMAS, not BODMAS.",{"type":378,"tag":1696,"props":2939,"children":2940},{},[2941],{"type":378,"tag":1549,"props":2942,"children":2943},{},[2944],{"type":390,"value":2945},"Update 3/12: you did this and put it in the code. Can't remember if you actually employed it or just let it sit there for future use",{"title":369,"searchDepth":481,"depth":481,"links":2947},[],"content:notes-to-self:a-mathematical-expression-parser-in-typescript.md","notes-to-self/a-mathematical-expression-parser-in-typescript.md","notes-to-self/a-mathematical-expression-parser-in-typescript",{"_path":2952,"_dir":367,"_draft":368,"_partial":368,"_locale":369,"title":332,"description":2953,"slug":333,"date":2954,"dateString":2955,"encrypted":368,"encryptedBody":373,"body":2956,"_type":485,"_id":3068,"_source":487,"_file":3069,"_stem":3070,"_extension":490},"/notes-to-self/vue-and-vue-adjacent","Generate documentation from .vue files?",1674493200000,"2023.01.23",{"type":375,"children":2957,"toc":3060},[2958,2971,3010,3016,3022,3027,3038,3044,3050],{"type":378,"tag":379,"props":2959,"children":2960},{},[2961,2963,2969],{"type":390,"value":2962},"Generate documentation from ",{"type":378,"tag":533,"props":2964,"children":2966},{"className":2965},[],[2967],{"type":390,"value":2968},".vue",{"type":390,"value":2970}," files?",{"type":378,"tag":1696,"props":2972,"children":2973},{},[2974,2979,2984,2989,2994,3005],{"type":378,"tag":1549,"props":2975,"children":2976},{},[2977],{"type":390,"value":2978},"Storybook",{"type":378,"tag":1549,"props":2980,"children":2981},{},[2982],{"type":390,"value":2983},"Vitepress",{"type":378,"tag":1549,"props":2985,"children":2986},{},[2987],{"type":390,"value":2988},"Vitesse",{"type":378,"tag":1549,"props":2990,"children":2991},{},[2992],{"type":390,"value":2993},"Nuxt \"docus\" theme",{"type":378,"tag":1549,"props":2995,"children":2996},{},[2997,3003],{"type":378,"tag":383,"props":2998,"children":3001},{"href":2999,"rel":3000},"https://histoire.dev",[387],[3002],{"type":390,"value":2999},{"type":390,"value":3004}," - by Vue 3 dev? What is this \"story\" term?",{"type":378,"tag":1549,"props":3006,"children":3007},{},[3008],{"type":390,"value":3009},"Swimm",{"type":378,"tag":3011,"props":3012,"children":3014},"h2",{"id":3013},"_20230302",[3015],{"type":390,"value":2875},{"type":378,"tag":462,"props":3017,"children":3019},{"id":3018},"vue-3-modals-and-teleport",[3020],{"type":390,"value":3021},"Vue 3, modals, and teleport",{"type":378,"tag":379,"props":3023,"children":3024},{},[3025],{"type":390,"value":3026},"For reasons you have lost track of, with Nuxt 3, you must create a plugin to append HTML to body template (you vaguely recall you can prepend stuff via config?). In your case, you wanted an html element to act as container for modals. Plugin looks like this:",{"type":378,"tag":525,"props":3028,"children":3033},{"className":3029,"code":3031,"language":3032,"meta":369},[3030],"language-typescript","\n// server/plugins/teleportTarget.ts\n\nimport { defineNitroPlugin } from 'nitropack/runtime/plugin';\n\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('render:html', (html, { event }) => {\n    html.bodyAppend.push('\u003Cdiv id=\"modals\">\u003C/div>');\n  });\n});\n","typescript",[3034],{"type":378,"tag":533,"props":3035,"children":3036},{"__ignoreMap":369},[3037],{"type":390,"value":3031},{"type":378,"tag":3011,"props":3039,"children":3041},{"id":3040},"_20230323",[3042],{"type":390,"value":3043},"2023.03.23",{"type":378,"tag":462,"props":3045,"children":3047},{"id":3046},"making-a-package-others-can-use",[3048],{"type":390,"value":3049},"Making a Package Others Can Use",{"type":378,"tag":379,"props":3051,"children":3052},{},[3053],{"type":378,"tag":383,"props":3054,"children":3057},{"href":3055,"rel":3056},"https://vueschool.io/articles/vuejs-tutorials/how-to-package-and-distribute-a-vue-js-3-plugin-on-npm/",[387],[3058],{"type":390,"value":3059},"Untested",{"title":369,"searchDepth":481,"depth":481,"links":3061},[3062,3065],{"id":3013,"depth":481,"text":2875,"children":3063},[3064],{"id":3018,"depth":484,"text":3021},{"id":3040,"depth":481,"text":3043,"children":3066},[3067],{"id":3046,"depth":484,"text":3049},"content:notes-to-self:vue-and-vue-adjacent.md","notes-to-self/vue-and-vue-adjacent.md","notes-to-self/vue-and-vue-adjacent",{"_path":3072,"_dir":367,"_draft":368,"_partial":368,"_locale":369,"title":329,"description":3073,"slug":330,"date":3074,"dateString":3075,"encrypted":368,"encryptedBody":373,"body":3076,"_type":485,"_id":4036,"_source":487,"_file":4037,"_stem":4038,"_extension":490},"/notes-to-self/vue-3-nuxt-nuxt-content-typescript","Having a love/hate relationship with Vue 3 and Nuxt 3. One of the pain points is that most of modern JavaScript relies on TypeScript, which in turn has \"first class\" tooling in VS Code (\"Visual Studio Code\").",1674406800000,"2023.01.22",{"type":375,"children":3077,"toc":4022},[3078,3082,3087,3092,3097,3102,3115,3120,3125,3143,3157,3163,3194,3200,3209,3214,3298,3304,3313,3319,3328,3337,3343,3348,3353,3358,3369,3382,3391,3396,3409,3415,3420,3441,3461,3466,3477,3482,3488,3493,3555,3560,3604,3609,3614,3623,3628,3637,3649,3654,3749,3754,3760,3765,3771,3776,3785,3790,3796,3825,3838,3847,3872,3877,3883,3888,3897,3910,3919,3925,3930,3939,3959,3968,3973,3982,3995,4004,4010],{"type":378,"tag":379,"props":3079,"children":3080},{},[3081],{"type":390,"value":3073},{"type":378,"tag":379,"props":3083,"children":3084},{},[3085],{"type":390,"value":3086},"For context, TypeScript is a language that adds typing to JavaScript. This typing is only manifested in the code editor or in the tools that compile the TypeScript into JavaScript.",{"type":378,"tag":379,"props":3088,"children":3089},{},[3090],{"type":390,"value":3091},"Microsoft controls both TypeScript and VS Code. VS Code is loved by many but is not a \"native\" app -- slower, less native UI, more memory than my preferred tool, Sublime.",{"type":378,"tag":379,"props":3093,"children":3094},{},[3095],{"type":390,"value":3096},"So Vue \"strongly recommends\" using VS Code, but I want to use Sublime.",{"type":378,"tag":379,"props":3098,"children":3099},{},[3100],{"type":390,"value":3101},"The TypeScript tools run in the background and communicate with the editor when there is something wrong. I have them installed on Sublime, and they work, although the UI is not great (problems sometimes manifested by red underlines, sometimes by a text panel that appears at the bottom; no idea how to dismiss the text panel or how to make it reappear). And then sometimes it just stops working, silently. The only way to know is by making an intentional error. Restarting Sublime is the only solution.",{"type":378,"tag":379,"props":3103,"children":3104},{},[3105,3107,3113],{"type":390,"value":3106},"Nuxt in particular does \"magic\" where certain functions are automatically imported. Using mysterytheaterbrowser.org as a test, I thought it wouldn't work at all, e.g. the ",{"type":378,"tag":533,"props":3108,"children":3110},{"className":3109},[],[3111],{"type":390,"value":3112},"useAsyncData",{"type":390,"value":3114}," function was not defined.",{"type":378,"tag":379,"props":3116,"children":3117},{},[3118],{"type":390,"value":3119},"But after restarting Sublime, it appears to be OK.",{"type":378,"tag":379,"props":3121,"children":3122},{},[3123],{"type":390,"value":3124},"There are three things I got working with BC project (Vue 3, not Nuxt) that I want working with a Nuxt project:",{"type":378,"tag":1545,"props":3126,"children":3127},{},[3128,3133,3138],{"type":378,"tag":1549,"props":3129,"children":3130},{},[3131],{"type":390,"value":3132},"Sublime reporting errors (this is done)",{"type":378,"tag":1549,"props":3134,"children":3135},{},[3136],{"type":390,"value":3137},"Errors on command line. Maybe that inefficiently runs a different TypeScript checking service, but with BC project, it is a more reliable way to review and understand errors.",{"type":378,"tag":1549,"props":3139,"children":3140},{},[3141],{"type":390,"value":3142},"Pre-commit code formatting",{"type":378,"tag":379,"props":3144,"children":3145},{},[3146,3148,3155],{"type":390,"value":3147},"#1 is done. #2 is being held up by ",{"type":378,"tag":383,"props":3149,"children":3152},{"href":3150,"rel":3151},"https://github.com/fi3ework/vite-plugin-checker/issues/197",[387],[3153],{"type":390,"value":3154},"a problem in a related module",{"type":390,"value":3156}," (jiti), #3 solution is below.",{"type":378,"tag":462,"props":3158,"children":3160},{"id":3159},"pre-commit-code-formatting-eslint-prettier-lint-staged-and-husky",[3161],{"type":390,"value":3162},"Pre-commit Code Formatting - eslint, prettier, lint-staged, and husky",{"type":378,"tag":379,"props":3164,"children":3165},{},[3166,3171,3173,3178,3180,3185,3187,3192],{"type":378,"tag":439,"props":3167,"children":3168},{},[3169],{"type":390,"value":3170},"eslint",{"type":390,"value":3172}," is a code syntax checker/fixer and ",{"type":378,"tag":439,"props":3174,"children":3175},{},[3176],{"type":390,"value":3177},"prettier",{"type":390,"value":3179}," is a code format checker/fixer. ",{"type":378,"tag":439,"props":3181,"children":3182},{},[3183],{"type":390,"value":3184},"lint-staged",{"type":390,"value":3186}," is a node tool for running linters as git hooks, and ",{"type":378,"tag":439,"props":3188,"children":3189},{},[3190],{"type":390,"value":3191},"husky",{"type":390,"value":3193}," is a node tool for managing git hooks.",{"type":378,"tag":625,"props":3195,"children":3197},{"id":3196},"_1-install-and-configure-lint-stage-eslint-and-prettier",[3198],{"type":390,"value":3199},"1. Install and configure lint-stage, eslint and prettier",{"type":378,"tag":525,"props":3201,"children":3204},{"className":3202,"code":3203,"language":1679,"meta":369},[1677],"$ yarn add -D lint-staged eslint @nuxtjs/eslint-config-typescript typescript eslint-plugin-prettier eslint-config-prettier prettier\n",[3205],{"type":378,"tag":533,"props":3206,"children":3207},{"__ignoreMap":369},[3208],{"type":390,"value":3203},{"type":378,"tag":379,"props":3210,"children":3211},{},[3212],{"type":390,"value":3213},"Note the purpose of the less-obvious packages:",{"type":378,"tag":1696,"props":3215,"children":3216},{},[3217,3281],{"type":378,"tag":1549,"props":3218,"children":3219},{},[3220,3222,3228,3230,3236,3238],{"type":390,"value":3221},"used in ",{"type":378,"tag":533,"props":3223,"children":3225},{"className":3224},[],[3226],{"type":390,"value":3227},".eslintrc.cjs",{"type":390,"value":3229},", in ",{"type":378,"tag":533,"props":3231,"children":3233},{"className":3232},[],[3234],{"type":390,"value":3235},"extends",{"type":390,"value":3237}," array. Order matters.\n",{"type":378,"tag":1696,"props":3239,"children":3240},{},[3241,3252,3270],{"type":378,"tag":1549,"props":3242,"children":3243},{},[3244,3250],{"type":378,"tag":533,"props":3245,"children":3247},{"className":3246},[],[3248],{"type":390,"value":3249},"@nuxtjs/eslint-config-typescript",{"type":390,"value":3251},": configures eslint for a nuxt project, used in",{"type":378,"tag":1549,"props":3253,"children":3254},{},[3255,3261,3263,3268],{"type":378,"tag":533,"props":3256,"children":3258},{"className":3257},[],[3259],{"type":390,"value":3260},"eslint-config-prettier",{"type":390,"value":3262},": turns ",{"type":378,"tag":439,"props":3264,"children":3265},{},[3266],{"type":390,"value":3267},"off",{"type":390,"value":3269}," things in eslint that conflict with things in prettier",{"type":378,"tag":1549,"props":3271,"children":3272},{},[3273,3279],{"type":378,"tag":533,"props":3274,"children":3276},{"className":3275},[],[3277],{"type":390,"value":3278},"eslint-plugin-prettier",{"type":390,"value":3280},": does some configuration of things that eslint needs, e.g. the prettier plugin",{"type":378,"tag":1549,"props":3282,"children":3283},{},[3284,3289,3291,3296],{"type":378,"tag":533,"props":3285,"children":3287},{"className":3286},[],[3288],{"type":390,"value":3032},{"type":390,"value":3290}," package must be expressly installed for the ",{"type":378,"tag":533,"props":3292,"children":3294},{"className":3293},[],[3295],{"type":390,"value":3249},{"type":390,"value":3297}," package",{"type":378,"tag":625,"props":3299,"children":3301},{"id":3300},"_2-configure-prettier-and-eslint",[3302],{"type":390,"value":3303},"2. Configure prettier and eslint",{"type":378,"tag":525,"props":3305,"children":3308},{"className":3306,"code":3307,"language":618,"meta":369},[616],"// .eslintrc.cjs\n\nmodule.exports = {\n  root: true,\n  extends: [\n    '@nuxtjs/eslint-config-typescript',\n    'plugin:prettier/recommended',\n  ],\n  rules: {\n    'no-unused-vars': 'off',\n    '@typescript-eslint/no-unused-vars': 'warn',\n  },\n};\n\n// .prettierrc.json\n\n{\n  \"singleQuote\": true // optional\n}\n",[3309],{"type":378,"tag":533,"props":3310,"children":3311},{"__ignoreMap":369},[3312],{"type":390,"value":3307},{"type":378,"tag":625,"props":3314,"children":3316},{"id":3315},"_3-install-husky-and-set-up",[3317],{"type":390,"value":3318},"3. Install husky and set up",{"type":378,"tag":525,"props":3320,"children":3323},{"className":3321,"code":3322,"language":1679,"meta":369},[1677],"$ yarn add husky --dev # install package\n$ # before with husky V8\n$ yarn husky install # it creates needed files\n$ npx husky add .husky/pre-commit \"yarn lint-staged\" # make hook\n$ # NOW with husky V9\n$ npx husky\n$ echo \"npx lint-staged\" > .husky/pre-commit\n",[3324],{"type":378,"tag":533,"props":3325,"children":3326},{"__ignoreMap":369},[3327],{"type":390,"value":3322},{"type":378,"tag":525,"props":3329,"children":3332},{"className":3330,"code":3331,"language":2515,"meta":369},[2513],"// package.json\n{\n    \"scripts\": {\n    ...\n    \"postinstall\": \"nuxt prepare && husky install\",\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\"\n    },\n    ...\n    // this is what happens when the hook above is invoked\n    \"lint-staged\": {\n    \"*.{js,jsx,vue,ts,tsx}\":\n    \"eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix\"\n  }\n}\n",[3333],{"type":378,"tag":533,"props":3334,"children":3335},{"__ignoreMap":369},[3336],{"type":390,"value":3331},{"type":378,"tag":462,"props":3338,"children":3340},{"id":3339},"lessons-learned-re-nuxt-pinia-new-store-transitions",[3341],{"type":390,"value":3342},"Lessons learned re: Nuxt, Pinia (new store), Transitions",{"type":378,"tag":379,"props":3344,"children":3345},{},[3346],{"type":390,"value":3347},"You wish to be able to dynamically specify page transitions based on whatever attributes of content you wish (not just what's in the route path).",{"type":378,"tag":379,"props":3349,"children":3350},{},[3351],{"type":390,"value":3352},"Arguably, this is a concern outside of the purview of Nuxt — it's a purely in-browser, dom-related decoration.",{"type":378,"tag":379,"props":3354,"children":3355},{},[3356],{"type":390,"value":3357},"But, nuxt provides a mechanism for specifying the page transition, and so using the store and reactive variables becomes temptingly tidy.",{"type":378,"tag":525,"props":3359,"children":3364},{"className":3360,"code":3362,"language":3363,"meta":369},[3361],"language-html","\u003C!-- in app.vue -->\n\u003CNuxtPage\n  :transition=\"{\n    name: 'some-transition',\n    mode: 'out-in'\n  }\"\n/>\n","html",[3365],{"type":378,"tag":533,"props":3366,"children":3367},{"__ignoreMap":369},[3368],{"type":390,"value":3362},{"type":378,"tag":379,"props":3370,"children":3371},{},[3372,3374,3380],{"type":390,"value":3373},"Nuxt also provides a mechanism in an individual page template for defining its transition (",{"type":378,"tag":533,"props":3375,"children":3377},{"className":3376},[],[3378],{"type":390,"value":3379},"definePageMeta({ pageTransition: 'some-value' })",{"type":390,"value":3381},"), but it's not suitable because it can only be a static value.",{"type":378,"tag":525,"props":3383,"children":3386},{"className":3384,"code":3385,"language":530,"meta":369},[528],"// we can't use definePageMeta b/c it is a not a real function - it's a macro\n// and cannot refer to local vars\ndefinePageMeta({\n  pageTransition: {\n    name: 'some-transition' // ok\n    // name: someRef.value // error (counter is not defined)\n    // name: useSomeStore().someValue // error (no active Pinia)\n  }\n})\n",[3387],{"type":378,"tag":533,"props":3388,"children":3389},{"__ignoreMap":369},[3390],{"type":390,"value":3385},{"type":378,"tag":379,"props":3392,"children":3393},{},[3394],{"type":390,"value":3395},"So, we need a store/computed value to hold the transition.",{"type":378,"tag":379,"props":3397,"children":3398},{},[3399,3401,3407],{"type":390,"value":3400},"The transition cannot be altered during the incoming page's ",{"type":378,"tag":533,"props":3402,"children":3404},{"className":3403},[],[3405],{"type":390,"value":3406},"\u003Cscript setup>",{"type":390,"value":3408}," lifecycle. And we can't embed dynamic info in the route's meta, or, as above, the page meta. So we are left with our custom link type in which we can declare the relationship of the linked-to content to the currently presented content.",{"type":378,"tag":462,"props":3410,"children":3412},{"id":3411},"lessons-learned-continued-nuxt-content-search",[3413],{"type":390,"value":3414},"Lessons learned continued: Nuxt Content, Search",{"type":378,"tag":379,"props":3416,"children":3417},{},[3418],{"type":390,"value":3419},"Nuxt Content 2 is much changed, and not as well documented. The documentation does not distinguish between what is available in a statically generated site and what is not.",{"type":378,"tag":379,"props":3421,"children":3422},{},[3423,3425,3431,3433,3439],{"type":390,"value":3424},"Gone is ",{"type":378,"tag":533,"props":3426,"children":3428},{"className":3427},[],[3429],{"type":390,"value":3430},"search",{"type":390,"value":3432}," which magically created a large static index of the content type to be searched. You can use ",{"type":378,"tag":533,"props":3434,"children":3436},{"className":3435},[],[3437],{"type":390,"value":3438},"where",{"type":390,"value":3440}," clauses but it appears that if it contains conditions that aren't present at render time, no index is created to magically load and search through. There are no errors generated - just on the static site, resources will not be found. Presumably there is some hash being created by the query being used to generate deterministic file names.",{"type":378,"tag":379,"props":3442,"children":3443},{},[3444,3446,3452,3454,3459],{"type":390,"value":3445},"Also, possibly only top-level calls to ",{"type":378,"tag":533,"props":3447,"children":3449},{"className":3448},[],[3450],{"type":390,"value":3451},"queryContent",{"type":390,"value":3453}," within ",{"type":378,"tag":533,"props":3455,"children":3457},{"className":3456},[],[3458],{"type":390,"value":3112},{"type":390,"value":3460}," create the necessary static resources. That is, `queryContent().then(() => queryContent()) does not work.",{"type":378,"tag":379,"props":3462,"children":3463},{},[3464],{"type":390,"value":3465},"Even this does not work on a statically generated site:",{"type":378,"tag":525,"props":3467,"children":3472},{"className":3468,"code":3470,"language":3471,"meta":369},[3469],"language-vue","\u003Ctemplate>\n  \u003Cdiv class=\"bg-white\">\n    \u003Ch1>Search results\u003C/h1>\n    \u003Cbutton @click=\"test\">Test\u003C/button>\n    \u003Cpre>{{ data }}\u003C/pre>\n  \u003C/div>\n\u003C/template>\n\n\u003Cscript setup lang=\"ts\">\n\nconst episodeNumbers = ref([1, 2])\n\nconst data = await useAsyncData('search-test', () => {\n  return queryContent('episodes')\n    .where({ id: { $in: episodeNumbers.value}})\n    .only(['id', 'title'])\n    .find()\n});\n\nconst test = () => {\n  episodeNumbers.value[0] = episodeNumbers.value[0] + 1\n  episodeNumbers.value[1] = episodeNumbers.value[1] + 1\n  data.refresh()\n}\n\u003C/script>\n","vue",[3473],{"type":378,"tag":533,"props":3474,"children":3475},{"__ignoreMap":369},[3476],{"type":390,"value":3470},{"type":378,"tag":379,"props":3478,"children":3479},{},[3480],{"type":390,"value":3481},"Your viable solution: make a secret page that loads all the context you want to index. When you want to search it, use the exact same query in the component doing the searching, and manually filter the results.",{"type":378,"tag":462,"props":3483,"children":3485},{"id":3484},"nuxt-3-nuxt-content-2-bad-build-times",[3486],{"type":390,"value":3487},"Nuxt 3 + Nuxt Content 2 = bad build times?",{"type":378,"tag":379,"props":3489,"children":3490},{},[3491],{"type":390,"value":3492},"Getting really slow build times for updated Mystery Theater.",{"type":378,"tag":2093,"props":3494,"children":3495},{},[3496,3517],{"type":378,"tag":2097,"props":3497,"children":3498},{},[3499],{"type":378,"tag":2101,"props":3500,"children":3501},{},[3502,3507,3512],{"type":378,"tag":2105,"props":3503,"children":3504},{},[3505],{"type":390,"value":3506},"Version",{"type":378,"tag":2105,"props":3508,"children":3509},{},[3510],{"type":390,"value":3511},"Local",{"type":378,"tag":2105,"props":3513,"children":3514},{},[3515],{"type":390,"value":3516},"Netlify",{"type":378,"tag":2126,"props":3518,"children":3519},{},[3520,3538],{"type":378,"tag":2101,"props":3521,"children":3522},{},[3523,3528,3533],{"type":378,"tag":2133,"props":3524,"children":3525},{},[3526],{"type":390,"value":3527},"Nuxt 2 / Content 1",{"type":378,"tag":2133,"props":3529,"children":3530},{},[3531],{"type":390,"value":3532},"25sec",{"type":378,"tag":2133,"props":3534,"children":3535},{},[3536],{"type":390,"value":3537},"4min",{"type":378,"tag":2101,"props":3539,"children":3540},{},[3541,3546,3550],{"type":378,"tag":2133,"props":3542,"children":3543},{},[3544],{"type":390,"value":3545},"Nuxt 3 / Content 2",{"type":378,"tag":2133,"props":3547,"children":3548},{},[3549],{"type":390,"value":3537},{"type":378,"tag":2133,"props":3551,"children":3552},{},[3553],{"type":390,"value":3554},"11min",{"type":378,"tag":379,"props":3556,"children":3557},{},[3558],{"type":390,"value":3559},"Made a bare bones version of site in Nuxt 2 and Nuxt 3 - basically just an index page pointing to all 1399 episode pages with the following results:",{"type":378,"tag":2093,"props":3561,"children":3562},{},[3563,3577],{"type":378,"tag":2097,"props":3564,"children":3565},{},[3566],{"type":378,"tag":2101,"props":3567,"children":3568},{},[3569,3573],{"type":378,"tag":2105,"props":3570,"children":3571},{},[3572],{"type":390,"value":3506},{"type":378,"tag":2105,"props":3574,"children":3575},{},[3576],{"type":390,"value":3511},{"type":378,"tag":2126,"props":3578,"children":3579},{},[3580,3592],{"type":378,"tag":2101,"props":3581,"children":3582},{},[3583,3587],{"type":378,"tag":2133,"props":3584,"children":3585},{},[3586],{"type":390,"value":3527},{"type":378,"tag":2133,"props":3588,"children":3589},{},[3590],{"type":390,"value":3591},"11sec",{"type":378,"tag":2101,"props":3593,"children":3594},{},[3595,3599],{"type":378,"tag":2133,"props":3596,"children":3597},{},[3598],{"type":390,"value":3545},{"type":378,"tag":2133,"props":3600,"children":3601},{},[3602],{"type":390,"value":3603},"16sec",{"type":378,"tag":379,"props":3605,"children":3606},{},[3607],{"type":390,"value":3608},"Adding link calculation only added 2 seconds.",{"type":378,"tag":379,"props":3610,"children":3611},{},[3612],{"type":390,"value":3613},"Ah -- adding artists (with episodes) added 3 minutes. Probably episode look ups per artist. Confirmed… removing episode look ups reduced to 21 seconds. Two lookups, both look like:",{"type":378,"tag":525,"props":3615,"children":3618},{"className":3616,"code":3617,"language":3032,"meta":369},[3030],"const { data: actor } = await useAsyncData(`episodes-actor-${id}`, () =>\n  queryContent('episodes')\n    .where({ actorIds: { $contains: id } })\n    .only(episodeProperties)\n    .sort({ id: 1, $numeric: true })\n    .find()\n);\n",[3619],{"type":378,"tag":533,"props":3620,"children":3621},{"__ignoreMap":369},[3622],{"type":390,"value":3617},{"type":378,"tag":379,"props":3624,"children":3625},{},[3626],{"type":390,"value":3627},"Let's attempt storing episode IDs with the actor? That reduced the time, but only by a third, 2:36.",{"type":378,"tag":525,"props":3629,"children":3632},{"className":3630,"code":3631,"language":3032,"meta":369},[3030],"const { data: actor } = await useAsyncData(`episodes-actor-${id}`, () =>\n  queryContent('episodes')\n    .where({ id: { $in: artist.value.actor ?? [] } })\n    .only(episodeProperties)\n    .sort({ id: 1, $numeric: true })\n    .find()\n);\n",[3633],{"type":378,"tag":533,"props":3634,"children":3635},{"__ignoreMap":369},[3636],{"type":390,"value":3631},{"type":378,"tag":379,"props":3638,"children":3639},{},[3640,3642,3647],{"type":390,"value":3641},"Sigh. Storing the episode title (removing the extra ",{"type":378,"tag":533,"props":3643,"children":3645},{"className":3644},[],[3646],{"type":390,"value":3451},{"type":390,"value":3648}," altogether) along with its ID gives enough info for the artist's page and reduces generation time to 25 seconds.",{"type":378,"tag":379,"props":3650,"children":3651},{},[3652],{"type":390,"value":3653},"Applying the same change to the actual mysterytheater.org, we get:",{"type":378,"tag":2093,"props":3655,"children":3656},{},[3657,3681],{"type":378,"tag":2097,"props":3658,"children":3659},{},[3660],{"type":378,"tag":2101,"props":3661,"children":3662},{},[3663,3667,3672,3677],{"type":378,"tag":2105,"props":3664,"children":3665},{},[3666],{"type":390,"value":3506},{"type":378,"tag":2105,"props":3668,"children":3669},{},[3670],{"type":390,"value":3671},"Local Bare",{"type":378,"tag":2105,"props":3673,"children":3674},{},[3675],{"type":390,"value":3676},"Local Full",{"type":378,"tag":2105,"props":3678,"children":3679},{},[3680],{"type":390,"value":3516},{"type":378,"tag":2126,"props":3682,"children":3683},{},[3684,3706,3727],{"type":378,"tag":2101,"props":3685,"children":3686},{},[3687,3691,3696,3701],{"type":378,"tag":2133,"props":3688,"children":3689},{},[3690],{"type":390,"value":3527},{"type":378,"tag":2133,"props":3692,"children":3693},{},[3694],{"type":390,"value":3695},"00:16",{"type":378,"tag":2133,"props":3697,"children":3698},{},[3699],{"type":390,"value":3700},"00:25",{"type":378,"tag":2133,"props":3702,"children":3703},{},[3704],{"type":390,"value":3705},"04:00",{"type":378,"tag":2101,"props":3707,"children":3708},{},[3709,3713,3718,3722],{"type":378,"tag":2133,"props":3710,"children":3711},{},[3712],{"type":390,"value":3545},{"type":378,"tag":2133,"props":3714,"children":3715},{},[3716],{"type":390,"value":3717},"03:16",{"type":378,"tag":2133,"props":3719,"children":3720},{},[3721],{"type":390,"value":3705},{"type":378,"tag":2133,"props":3723,"children":3724},{},[3725],{"type":390,"value":3726},"11:00",{"type":378,"tag":2101,"props":3728,"children":3729},{},[3730,3735,3739,3744],{"type":378,"tag":2133,"props":3731,"children":3732},{},[3733],{"type":390,"value":3734},"Nuxt 3 / Content 2 FLAT",{"type":378,"tag":2133,"props":3736,"children":3737},{},[3738],{"type":390,"value":3700},{"type":378,"tag":2133,"props":3740,"children":3741},{},[3742],{"type":390,"value":3743},"01:44",{"type":378,"tag":2133,"props":3745,"children":3746},{},[3747],{"type":390,"value":3748},"06:41",{"type":378,"tag":379,"props":3750,"children":3751},{},[3752],{"type":390,"value":3753},"Still room for improvement. Why is Nuxt 3 full still 3x bare? (Removing fetching of all episode IDs to get count of episodes on index page removed 40 seconds, about 40% less time)",{"type":378,"tag":3011,"props":3755,"children":3757},{"id":3756},"_20240225-update",[3758],{"type":390,"value":3759},"2024.02.25 Update",{"type":378,"tag":379,"props":3761,"children":3762},{},[3763],{"type":390,"value":3764},"Am very happy with Nuxt 3/Vue 3. Starting a new project from scratch. Documenting the steps I am currently taking.",{"type":378,"tag":462,"props":3766,"children":3768},{"id":3767},"_0-make-new-nuxt-project",[3769],{"type":390,"value":3770},"0. Make New Nuxt Project",{"type":378,"tag":379,"props":3772,"children":3773},{},[3774],{"type":390,"value":3775},"Using node 18.13.0.",{"type":378,"tag":525,"props":3777,"children":3780},{"className":3778,"code":3779,"language":1679,"meta":369},[1677],"npx nuxi@latest init \u003Cproject-name>\n",[3781],{"type":378,"tag":533,"props":3782,"children":3783},{"__ignoreMap":369},[3784],{"type":390,"value":3779},{"type":378,"tag":379,"props":3786,"children":3787},{},[3788],{"type":390,"value":3789},"This appears to have typescript installed.",{"type":378,"tag":625,"props":3791,"children":3793},{"id":3792},"_01-replace-welcome-with-actual-page",[3794],{"type":390,"value":3795},"0.1. Replace Welcome With Actual Page",{"type":378,"tag":379,"props":3797,"children":3798},{},[3799,3801,3807,3809,3815,3817,3823],{"type":390,"value":3800},"In ",{"type":378,"tag":533,"props":3802,"children":3804},{"className":3803},[],[3805],{"type":390,"value":3806},"app.vue",{"type":390,"value":3808}," replace ",{"type":378,"tag":533,"props":3810,"children":3812},{"className":3811},[],[3813],{"type":390,"value":3814},"\u003CNuxtWelcome />",{"type":390,"value":3816}," with ",{"type":378,"tag":533,"props":3818,"children":3820},{"className":3819},[],[3821],{"type":390,"value":3822},"\u003CNuxtPage />",{"type":390,"value":3824},".",{"type":378,"tag":379,"props":3826,"children":3827},{},[3828,3830,3836],{"type":390,"value":3829},"Create file ",{"type":378,"tag":533,"props":3831,"children":3833},{"className":3832},[],[3834],{"type":390,"value":3835},"pages/index.vue",{"type":390,"value":3837}," and give it contents like:",{"type":378,"tag":525,"props":3839,"children":3842},{"className":3840,"code":3841,"language":3471,"meta":369},[3469],"\u003Cscript setup lang=\"ts\">\nuseHead({\n  title: 'Hello, world',\n});\n\u003C/script>\n\u003Ctemplate>\n  \u003Cp>\n    Hello, world\n  \u003C/p>\n\u003C/template>\n",[3843],{"type":378,"tag":533,"props":3844,"children":3845},{"__ignoreMap":369},[3846],{"type":390,"value":3841},{"type":378,"tag":462,"props":3848,"children":3850},{"id":3849},"_1-2-install-and-configure-eslint-prettier-lint-stage",[3851,3853,3858,3860,3865,3866],{"type":390,"value":3852},"1. & 2. Install and Configure ",{"type":378,"tag":533,"props":3854,"children":3856},{"className":3855},[],[3857],{"type":390,"value":3170},{"type":390,"value":3859},", ",{"type":378,"tag":533,"props":3861,"children":3863},{"className":3862},[],[3864],{"type":390,"value":3177},{"type":390,"value":3859},{"type":378,"tag":533,"props":3867,"children":3869},{"className":3868},[],[3870],{"type":390,"value":3871},"lint-stage",{"type":378,"tag":379,"props":3873,"children":3874},{},[3875],{"type":390,"value":3876},"Follow steps #1 #2 from before. Step 3 is now different as lint-staged and husky have changed.",{"type":378,"tag":462,"props":3878,"children":3880},{"id":3879},"_3-install-husky",[3881],{"type":390,"value":3882},"3. Install Husky",{"type":378,"tag":379,"props":3884,"children":3885},{},[3886],{"type":390,"value":3887},"Install husky:",{"type":378,"tag":525,"props":3889,"children":3892},{"className":3890,"code":3891,"language":1679,"meta":369},[1677],"$ yarn add -D husky\n$ npx husky\n$ echo \"npx lint-staged\" > .husky/pre-commit\n",[3893],{"type":378,"tag":533,"props":3894,"children":3895},{"__ignoreMap":369},[3896],{"type":390,"value":3891},{"type":378,"tag":379,"props":3898,"children":3899},{},[3900,3902,3908],{"type":390,"value":3901},"Configure lint-staged by creating a file ",{"type":378,"tag":533,"props":3903,"children":3905},{"className":3904},[],[3906],{"type":390,"value":3907},".lintstagedrc.json",{"type":390,"value":3909}," with these contents:",{"type":378,"tag":525,"props":3911,"children":3914},{"className":3912,"code":3913,"language":2515,"meta":369},[2513],"{\n  \"*.{js,jsx,vue,ts,tsx}\":\n    \"eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix\"\n}\n",[3915],{"type":378,"tag":533,"props":3916,"children":3917},{"__ignoreMap":369},[3918],{"type":390,"value":3913},{"type":378,"tag":462,"props":3920,"children":3922},{"id":3921},"_4-sass",[3923],{"type":390,"value":3924},"4. Sass",{"type":378,"tag":379,"props":3926,"children":3927},{},[3928],{"type":390,"value":3929},"Add sass",{"type":378,"tag":525,"props":3931,"children":3934},{"className":3932,"code":3933,"language":1679,"meta":369},[1677],"yarn add -D sass\n",[3935],{"type":378,"tag":533,"props":3936,"children":3937},{"__ignoreMap":369},[3938],{"type":390,"value":3933},{"type":378,"tag":379,"props":3940,"children":3941},{},[3942,3944,3950,3952,3957],{"type":390,"value":3943},"To automatically include some sass file, create file ",{"type":378,"tag":533,"props":3945,"children":3947},{"className":3946},[],[3948],{"type":390,"value":3949},"assets/scss/app.scss",{"type":390,"value":3951}," and modify ",{"type":378,"tag":533,"props":3953,"children":3955},{"className":3954},[],[3956],{"type":390,"value":1858},{"type":390,"value":3958}," to include it:",{"type":378,"tag":525,"props":3960,"children":3963},{"className":3961,"code":3962,"language":530,"meta":369},[528],"export default defineNuxtConfig({\n  // ...\n  css: [\"@/assets/scss/app.scss\"],\n  // ...\n});\n",[3964],{"type":378,"tag":533,"props":3965,"children":3966},{"__ignoreMap":369},[3967],{"type":390,"value":3962},{"type":378,"tag":379,"props":3969,"children":3970},{},[3971],{"type":390,"value":3972},"To automatically include some sass variables or mixins in any component (you wonder if it doesn’t adversely affect build time), modify nuxt config to:",{"type":378,"tag":525,"props":3974,"children":3977},{"className":3975,"code":3976,"language":530,"meta":369},[528],"export default defineNuxtConfig({\n  // ...\n    vite: {\n    css: {\n      preprocessorOptions: {\n        scss: {\n          additionalData:\n            '@use \"@/assets/scss/_variables.scss\" as *;',\n        },\n      },\n    },\n  },\n  // ...\n});\n",[3978],{"type":378,"tag":533,"props":3979,"children":3980},{"__ignoreMap":369},[3981],{"type":390,"value":3976},{"type":378,"tag":379,"props":3983,"children":3984},{},[3985,3987,3993],{"type":390,"value":3986},"You can also import ",{"type":378,"tag":533,"props":3988,"children":3990},{"className":3989},[],[3991],{"type":390,"value":3992},"scss",{"type":390,"value":3994}," on a component basis via:",{"type":378,"tag":525,"props":3996,"children":3999},{"className":3997,"code":3998,"language":3471,"meta":369},[3469],"\u003Cstyle lang=\"scss\">\n@import \"@/assets/scss/variables\";\n// ...\n\u003C/style>\n",[4000],{"type":378,"tag":533,"props":4001,"children":4002},{"__ignoreMap":369},[4003],{"type":390,"value":3998},{"type":378,"tag":462,"props":4005,"children":4007},{"id":4006},"_6-pinia",[4008],{"type":390,"value":4009},"6. Pinia",{"type":378,"tag":379,"props":4011,"children":4012},{},[4013,4020],{"type":378,"tag":383,"props":4014,"children":4017},{"href":4015,"rel":4016},"https://pinia.vuejs.org/ssr/nuxt.html",[387],[4018],{"type":390,"value":4019},"Official docs for Nuxt",{"type":390,"value":4021}," nothing special to do.",{"title":369,"searchDepth":481,"depth":481,"links":4023},[4024,4025,4026,4027,4028],{"id":3159,"depth":484,"text":3162},{"id":3339,"depth":484,"text":3342},{"id":3411,"depth":484,"text":3414},{"id":3484,"depth":484,"text":3487},{"id":3756,"depth":481,"text":3759,"children":4029},[4030,4031,4033,4034,4035],{"id":3767,"depth":484,"text":3770},{"id":3849,"depth":484,"text":4032},"1. & 2. Install and Configure eslint, prettier, lint-stage",{"id":3879,"depth":484,"text":3882},{"id":3921,"depth":484,"text":3924},{"id":4006,"depth":484,"text":4009},"content:notes-to-self:vue-3-nuxt-nuxt-content-typescript.md","notes-to-self/vue-3-nuxt-nuxt-content-typescript.md","notes-to-self/vue-3-nuxt-nuxt-content-typescript",{"_path":4040,"_dir":367,"_draft":368,"_partial":368,"_locale":369,"title":134,"description":369,"slug":135,"date":4041,"dateString":4042,"encrypted":368,"encryptedBody":373,"body":4043,"_type":485,"_id":4076,"_source":487,"_file":4077,"_stem":4078,"_extension":490},"/notes-to-self/illustrator-affinity-designer",1673974800000,"2023.01.17",{"type":375,"children":4044,"toc":4074},[4045,4069],{"type":378,"tag":1696,"props":4046,"children":4047},{},[4048,4059],{"type":378,"tag":1549,"props":4049,"children":4050},{},[4051,4053,4057],{"type":390,"value":4052},"Illustrator: Outline Stroke =>",{"type":378,"tag":4054,"props":4055,"children":4056},"br",{},[],{"type":390,"value":4058},"\nAffinity: Layer => Expand Stroke",{"type":378,"tag":1549,"props":4060,"children":4061},{},[4062,4064,4067],{"type":390,"value":4063},"Illustrator: Fit artboard to selection =>",{"type":378,"tag":4054,"props":4065,"children":4066},{},[],{"type":390,"value":4068},"\nAffinity: Artboard tool (below arrow tool, 2nd from top), choose Size: Selection in menu above, click insert dartboard next to menu: artboard may be right size but content not aligned. Use alignment tool to align to artboard (left, top)",{"type":378,"tag":379,"props":4070,"children":4071},{},[4072],{"type":390,"value":4073},"What does it do for soft line breaks?\nLike that",{"title":369,"searchDepth":481,"depth":481,"links":4075},[],"content:notes-to-self:illustrator-affinity-designer.md","notes-to-self/illustrator-affinity-designer.md","notes-to-self/illustrator-affinity-designer",1776608492378]