[{"data":1,"prerenderedAt":1024},["ShallowReactive",2],{"docs-navigation":3,"doc-\u002Fdev\u002Fclean-code\u002Fservices-return-callers-present":229,"surround-\u002Fdev\u002Fclean-code\u002Fservices-return-callers-present":821,"notes-index":826},[4,88,171,182,192,203,218],{"title":5,"path":6,"stem":7,"children":8},"Dev","\u002Fdev","1.dev",[9,12,26,36,50,56,78],{"title":10,"path":6,"stem":11},"Development","1.dev\u002Findex",{"title":13,"path":14,"stem":15,"children":16},"Laravel","\u002Fdev\u002Flaravel","1.dev\u002F1.laravel\u002Findex",[17,18,22],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21},"Eloquent Relationships","\u002Fdev\u002Flaravel\u002Feloquent-relationships","1.dev\u002F1.laravel\u002F1.eloquent-relationships",{"title":23,"path":24,"stem":25},"Laravel Homestead","\u002Fdev\u002Flaravel\u002Flaravel-homestead","1.dev\u002F1.laravel\u002F2.laravel-homestead",{"title":27,"path":28,"stem":29,"children":30},"Bash","\u002Fdev\u002Fbash","1.dev\u002F13.bash\u002Findex",[31,32],{"title":27,"path":28,"stem":29},{"title":33,"path":34,"stem":35},"My Bash RC","\u002Fdev\u002Fbash\u002Fbash-rc","1.dev\u002F13.bash\u002F2.bash-rc",{"title":37,"path":38,"stem":39,"children":40},"Clean Code","\u002Fdev\u002Fclean-code","1.dev\u002F15.clean-code\u002Findex",[41,42,46],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45},"Naming Things","\u002Fdev\u002Fclean-code\u002Fnaming-things","1.dev\u002F15.clean-code\u002F1.naming-things",{"title":47,"path":48,"stem":49},"Services Return, Callers Present","\u002Fdev\u002Fclean-code\u002Fservices-return-callers-present","1.dev\u002F15.clean-code\u002F2.services-return-callers-present",{"title":51,"path":52,"stem":53,"children":54},"AI Prompting","\u002Fdev\u002Fai-prompting","1.dev\u002F17.ai-prompting\u002Findex",[55],{"title":51,"path":52,"stem":53},{"title":57,"path":58,"stem":59,"children":60},"C#","\u002Fdev\u002Fcsharp","1.dev\u002F2.csharp\u002Findex",[61,62,66,70,74],{"title":57,"path":58,"stem":59},{"title":63,"path":64,"stem":65},"C# Basics","\u002Fdev\u002Fcsharp\u002Fcsharp-basics","1.dev\u002F2.csharp\u002F1.csharp-basics",{"title":67,"path":68,"stem":69},"Classes & Models","\u002Fdev\u002Fcsharp\u002Fclasses-and-models","1.dev\u002F2.csharp\u002F2.classes-and-models",{"title":71,"path":72,"stem":73},"Services & Helpers","\u002Fdev\u002Fcsharp\u002Fservices-and-helpers","1.dev\u002F2.csharp\u002F3.services-and-helpers",{"title":75,"path":76,"stem":77},"Project Structure","\u002Fdev\u002Fcsharp\u002Fproject-structure","1.dev\u002F2.csharp\u002F4.project-structure",{"title":79,"path":80,"stem":81,"children":82},"Git","\u002Fdev\u002Fgit","1.dev\u002F9.git\u002Findex",[83,84],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87},"Git Cheat Sheet","\u002Fdev\u002Fgit\u002Fgit-cheat-sheet","1.dev\u002F9.git\u002F1.git-cheat-sheet",{"title":89,"path":90,"stem":91,"children":92},"Teaching","\u002Fteaching","2.teaching",[93,95],{"title":89,"path":90,"stem":94},"2.teaching\u002Findex",{"title":96,"path":97,"stem":98,"children":99},"AppsDev","\u002Fteaching\u002Fapps-dev","2.teaching\u002F1.apps-dev\u002Findex",[100,101,119,137,147,161],{"title":96,"path":97,"stem":98},{"title":102,"path":103,"stem":104,"children":105},"Lesson Plans","\u002Fteaching\u002Fapps-dev\u002Flesson-plans","2.teaching\u002F1.apps-dev\u002F1.lesson-plans\u002Findex",[106,107,111,115],{"title":102,"path":103,"stem":104},{"title":108,"path":109,"stem":110},"Coding with AI","\u002Fteaching\u002Fapps-dev\u002Flesson-plans\u002Fcoding-with-ai","2.teaching\u002F1.apps-dev\u002F1.lesson-plans\u002F1.coding-with-ai",{"title":112,"path":113,"stem":114},"Coding with AI: Prompts & Reading JavaScript","\u002Fteaching\u002Fapps-dev\u002Flesson-plans\u002Fprompts-and-reading-js","2.teaching\u002F1.apps-dev\u002F1.lesson-plans\u002F2.prompts-and-reading-js",{"title":116,"path":117,"stem":118},"JavaScript for Beginners","\u002Fteaching\u002Fapps-dev\u002Flesson-plans\u002Fjavascript-for-beginners","2.teaching\u002F1.apps-dev\u002F1.lesson-plans\u002F3.javascript-for-beginners",{"title":120,"path":121,"stem":122,"children":123},"Activities","\u002Fteaching\u002Fapps-dev\u002Factivities","2.teaching\u002F1.apps-dev\u002F2.activities\u002Findex",[124,125,129,133],{"title":120,"path":121,"stem":122},{"title":126,"path":127,"stem":128},"Dynamic Portfolio Website","\u002Fteaching\u002Fapps-dev\u002Factivities\u002Fdynamic-portfolio-website","2.teaching\u002F1.apps-dev\u002F2.activities\u002F2.dynamic-portfolio-website",{"title":130,"path":131,"stem":132},"Mini Product Store","\u002Fteaching\u002Fapps-dev\u002Factivities\u002Fmini-product-store","2.teaching\u002F1.apps-dev\u002F2.activities\u002F3.mini-product-store",{"title":134,"path":135,"stem":136},"Semester Project","\u002Fteaching\u002Fapps-dev\u002Factivities\u002Fsemester-project","2.teaching\u002F1.apps-dev\u002F2.activities\u002F4.semester-project",{"title":138,"path":139,"stem":140,"children":141},"Exercises","\u002Fteaching\u002Fapps-dev\u002Fexercises","2.teaching\u002F1.apps-dev\u002F4.exercises\u002Findex",[142,143],{"title":138,"path":139,"stem":140},{"title":144,"path":145,"stem":146},"Read & Predict","\u002Fteaching\u002Fapps-dev\u002Fexercises\u002Fread-and-predict","2.teaching\u002F1.apps-dev\u002F4.exercises\u002F1.read-and-predict",{"title":148,"path":149,"stem":150,"children":151},"AI Prompts","\u002Fteaching\u002Fapps-dev\u002Fai-prompts","2.teaching\u002F1.apps-dev\u002F7.ai-prompts\u002Findex",[152,153,157],{"title":148,"path":149,"stem":150},{"title":154,"path":155,"stem":156},"Prompt Flow","\u002Fteaching\u002Fapps-dev\u002Fai-prompts\u002Fbasic-prompting","2.teaching\u002F1.apps-dev\u002F7.ai-prompts\u002F1.basic-prompting",{"title":158,"path":159,"stem":160},"Using OpenCode CLI Instead of Copy-Pasting from ChatGPT","\u002Fteaching\u002Fapps-dev\u002Fai-prompts\u002Fopencode-cli","2.teaching\u002F1.apps-dev\u002F7.ai-prompts\u002F2.opencode-cli",{"title":162,"path":163,"stem":164,"children":165},"Cheat Sheets","\u002Fteaching\u002Fapps-dev\u002Fcheat-sheets","2.teaching\u002F1.apps-dev\u002F8.cheat-sheets\u002Findex",[166,167],{"title":162,"path":163,"stem":164},{"title":168,"path":169,"stem":170},"How to Collaborate on GitHub","\u002Fteaching\u002Fapps-dev\u002Fcheat-sheets\u002Fgithub-collaboration","2.teaching\u002F1.apps-dev\u002F8.cheat-sheets\u002F1.github-collaboration",{"title":172,"path":173,"stem":174,"children":175},"Snippets","\u002Fsnippets","3.snippets",[176,178],{"title":172,"path":173,"stem":177},"3.snippets\u002Findex",{"title":179,"path":180,"stem":181},"Debounce and Throttle","\u002Fsnippets\u002Fdebounce-and-throttle","3.snippets\u002F1.debounce-and-throttle",{"title":162,"path":183,"stem":184,"children":185},"\u002Fcheat-sheets","4.cheat-sheets",[186,188],{"title":162,"path":183,"stem":187},"4.cheat-sheets\u002Findex",{"title":189,"path":190,"stem":191},"Vim Cheat Sheet","\u002Fcheat-sheets\u002Fvim-cheat-sheet","4.cheat-sheets\u002F1.vim-cheat-sheet",{"title":193,"path":194,"stem":195,"children":196},"Templates","\u002Ftemplates","5.templates",[197,199],{"title":193,"path":194,"stem":198},"5.templates\u002Findex",{"title":200,"path":201,"stem":202},"Pull Request Template","\u002Ftemplates\u002Fpull-request-template","5.templates\u002F1.pull-request-template",{"title":204,"path":205,"stem":206,"children":207},"Meta","\u002Fmeta","6.meta",[208,210,214],{"title":204,"path":205,"stem":209},"6.meta\u002Findex",{"title":211,"path":212,"stem":213},"Authoring Guide","\u002Fmeta\u002Fauthoring-guide","6.meta\u002F1.authoring-guide",{"title":215,"path":216,"stem":217},"Markdown Showcase","\u002Fmeta\u002Fmarkdown-showcase","6.meta\u002F2.markdown-showcase",{"title":219,"path":220,"stem":221,"children":222},"Troubleshooting","\u002Ftroubleshooting","7.troubleshooting",[223,225],{"title":219,"path":220,"stem":224},"7.troubleshooting\u002Findex",{"title":226,"path":227,"stem":228},"Fixing CORS Errors","\u002Ftroubleshooting\u002Fcors-errors","7.troubleshooting\u002F1.cors-errors",{"id":230,"title":47,"body":231,"category":37,"createdAt":802,"description":803,"draft":804,"extension":805,"featured":804,"keywords":806,"links":812,"meta":813,"navigation":596,"path":48,"readingTime":280,"seo":814,"stem":49,"tags":815,"updatedAt":802,"__hash__":820},"docs\u002F1.dev\u002F15.clean-code\u002F2.services-return-callers-present.md",{"type":232,"value":233,"toc":796},"minimark",[234,243,248,251,467,473,477,480,555,681,737,741,770,774,785,792],[235,236,237,238,242],"p",{},"One rule that quietly fixes a lot of design problems: ",[239,240,241],"strong",{},"a service computes and\nreturns; the caller presents."," The service should not know whether its result\nends up in a terminal, an HTTP response, a test assertion, or a log line.",[244,245,247],"h2",{"id":246},"the-smell","The smell",[235,249,250],{},"A login service that prints its own outcome:",[252,253,258],"pre",{"className":254,"code":255,"language":256,"meta":257,"style":257},"language-csharp shiki shiki-themes material-theme-lighter vitesse-light vitesse-dark","public class LoginService\n{\n    public void Login(string username, string password)\n    {\n        var user = Find(username, password);\n        if (user is not null)\n            Console.WriteLine($\"Welcome, {user.Username}!\");   \u002F\u002F ← service knows about the console\n        else\n            Console.WriteLine(\"Invalid credentials.\");\n    }\n}\n","csharp","",[259,260,261,278,285,321,327,355,380,429,435,455,461],"code",{"__ignoreMap":257},[262,263,266,270,274],"span",{"class":264,"line":265},"line",1,[262,267,269],{"class":268},"s5Kfy","public",[262,271,273],{"class":272},"s23sZ"," class",[262,275,277],{"class":276},"sD-vU"," LoginService\n",[262,279,281],{"class":264,"line":280},2,[262,282,284],{"class":283},"soVBu","{\n",[262,286,288,291,295,299,302,305,309,312,315,318],{"class":264,"line":287},3,[262,289,290],{"class":268},"    public",[262,292,294],{"class":293},"s8XtY"," void",[262,296,298],{"class":297},"sljsM"," Login",[262,300,301],{"class":283},"(",[262,303,304],{"class":293},"string",[262,306,308],{"class":307},"sYn-s"," username",[262,310,311],{"class":283},",",[262,313,314],{"class":293}," string",[262,316,317],{"class":307}," password",[262,319,320],{"class":283},")\n",[262,322,324],{"class":264,"line":323},4,[262,325,326],{"class":283},"    {\n",[262,328,330,333,336,339,342,344,348,350,352],{"class":264,"line":329},5,[262,331,332],{"class":272},"        var",[262,334,335],{"class":307}," user",[262,337,338],{"class":283}," =",[262,340,341],{"class":297}," Find",[262,343,301],{"class":283},[262,345,347],{"class":346},"sSC40","username",[262,349,311],{"class":283},[262,351,317],{"class":346},[262,353,354],{"class":283},");\n",[262,356,358,362,365,368,372,375,378],{"class":264,"line":357},6,[262,359,361],{"class":360},"siDh9","        if",[262,363,364],{"class":283}," (",[262,366,367],{"class":346},"user",[262,369,371],{"class":370},"sVsLi"," is",[262,373,374],{"class":370}," not",[262,376,377],{"class":370}," null",[262,379,320],{"class":283},[262,381,383,386,389,392,394,398,402,405,408,410,413,416,419,422,425],{"class":264,"line":382},7,[262,384,385],{"class":346},"            Console",[262,387,388],{"class":283},".",[262,390,391],{"class":297},"WriteLine",[262,393,301],{"class":283},[262,395,397],{"class":396},"sbYkP","$\"",[262,399,401],{"class":400},"sTbE_","Welcome, ",[262,403,404],{"class":283},"{",[262,406,367],{"class":407},"su_V2",[262,409,388],{"class":283},[262,411,412],{"class":407},"Username",[262,414,415],{"class":283},"}",[262,417,418],{"class":400},"!",[262,420,421],{"class":396},"\"",[262,423,424],{"class":283},");",[262,426,428],{"class":427},"s9Tkl","   \u002F\u002F ← service knows about the console\n",[262,430,432],{"class":264,"line":431},8,[262,433,434],{"class":360},"        else\n",[262,436,438,440,442,444,446,448,451,453],{"class":264,"line":437},9,[262,439,385],{"class":346},[262,441,388],{"class":283},[262,443,391],{"class":297},[262,445,301],{"class":283},[262,447,421],{"class":396},[262,449,450],{"class":400},"Invalid credentials.",[262,452,421],{"class":396},[262,454,354],{"class":283},[262,456,458],{"class":264,"line":457},10,[262,459,460],{"class":283},"    }\n",[262,462,464],{"class":264,"line":463},11,[262,465,466],{"class":283},"}\n",[235,468,469,470,388],{},"This works, but the service is now welded to the console. You can't reuse it in a\nweb API, you can't unit-test the outcome without capturing stdout, and the\n\"what happened\" is trapped inside a ",[259,471,472],{},"void",[244,474,476],{"id":475},"the-fix","The fix",[235,478,479],{},"Return the outcome. Let whoever called it decide what to do with it:",[252,481,483],{"className":254,"code":482,"language":256,"meta":257,"style":257},"public class LoginService\n{\n    public User? Login(string username, string password)\n    {\n        return Find(username, password);   \u002F\u002F just the result\n    }\n}\n",[259,484,485,493,497,523,527,547,551],{"__ignoreMap":257},[262,486,487,489,491],{"class":264,"line":265},[262,488,269],{"class":268},[262,490,273],{"class":272},[262,492,277],{"class":276},[262,494,495],{"class":264,"line":280},[262,496,284],{"class":283},[262,498,499,501,504,507,509,511,513,515,517,519,521],{"class":264,"line":287},[262,500,290],{"class":268},[262,502,503],{"class":276}," User",[262,505,506],{"class":283},"?",[262,508,298],{"class":297},[262,510,301],{"class":283},[262,512,304],{"class":293},[262,514,308],{"class":307},[262,516,311],{"class":283},[262,518,314],{"class":293},[262,520,317],{"class":307},[262,522,320],{"class":283},[262,524,525],{"class":264,"line":323},[262,526,326],{"class":283},[262,528,529,532,534,536,538,540,542,544],{"class":264,"line":329},[262,530,531],{"class":360},"        return",[262,533,341],{"class":297},[262,535,301],{"class":283},[262,537,347],{"class":346},[262,539,311],{"class":283},[262,541,317],{"class":346},[262,543,424],{"class":283},[262,545,546],{"class":427},"   \u002F\u002F just the result\n",[262,548,549],{"class":264,"line":357},[262,550,460],{"class":283},[262,552,553],{"class":264,"line":382},[262,554,466],{"class":283},[252,556,558],{"className":254,"code":557,"language":256,"meta":257,"style":257},"\u002F\u002F The CALLER presents — and this can differ per context:\nvar user = loginService.Login(username, password);\n\nif (user is not null)\n    Console.WriteLine($\"Welcome, {user.Username}!\");   \u002F\u002F console app\nelse\n    Console.WriteLine(\"Invalid credentials.\");\n\n\u002F\u002F A web API caller would instead: return user is not null ? Ok(user) : Unauthorized();\n",[259,559,560,565,592,598,615,649,654,672,676],{"__ignoreMap":257},[262,561,562],{"class":264,"line":265},[262,563,564],{"class":427},"\u002F\u002F The CALLER presents — and this can differ per context:\n",[262,566,567,570,572,574,577,579,582,584,586,588,590],{"class":264,"line":280},[262,568,569],{"class":272},"var",[262,571,335],{"class":307},[262,573,338],{"class":283},[262,575,576],{"class":346}," loginService",[262,578,388],{"class":283},[262,580,581],{"class":297},"Login",[262,583,301],{"class":283},[262,585,347],{"class":346},[262,587,311],{"class":283},[262,589,317],{"class":346},[262,591,354],{"class":283},[262,593,594],{"class":264,"line":287},[262,595,597],{"emptyLinePlaceholder":596},true,"\n",[262,599,600,603,605,607,609,611,613],{"class":264,"line":323},[262,601,602],{"class":360},"if",[262,604,364],{"class":283},[262,606,367],{"class":346},[262,608,371],{"class":370},[262,610,374],{"class":370},[262,612,377],{"class":370},[262,614,320],{"class":283},[262,616,617,620,622,624,626,628,630,632,634,636,638,640,642,644,646],{"class":264,"line":329},[262,618,619],{"class":346},"    Console",[262,621,388],{"class":283},[262,623,391],{"class":297},[262,625,301],{"class":283},[262,627,397],{"class":396},[262,629,401],{"class":400},[262,631,404],{"class":283},[262,633,367],{"class":407},[262,635,388],{"class":283},[262,637,412],{"class":407},[262,639,415],{"class":283},[262,641,418],{"class":400},[262,643,421],{"class":396},[262,645,424],{"class":283},[262,647,648],{"class":427},"   \u002F\u002F console app\n",[262,650,651],{"class":264,"line":357},[262,652,653],{"class":360},"else\n",[262,655,656,658,660,662,664,666,668,670],{"class":264,"line":382},[262,657,619],{"class":346},[262,659,388],{"class":283},[262,661,391],{"class":297},[262,663,301],{"class":283},[262,665,421],{"class":396},[262,667,450],{"class":400},[262,669,421],{"class":396},[262,671,354],{"class":283},[262,673,674],{"class":264,"line":431},[262,675,597],{"emptyLinePlaceholder":596},[262,677,678],{"class":264,"line":437},[262,679,680],{"class":427},"\u002F\u002F A web API caller would instead: return user is not null ? Ok(user) : Unauthorized();\n",[682,683,684,695],"tip",{},[235,685,686,687,690,691,694],{},"If the outcome is richer than \"found \u002F not found\" — say you need a reason for the\nfailure — return a small ",[239,688,689],{},"result object"," instead of ",[259,692,693],{},"null",", rather than\nreaching for the console:",[252,696,698],{"className":254,"code":697,"language":256,"meta":257,"style":257},"public record LoginResult(bool Success, User? User, string? Error);\n",[259,699,700],{"__ignoreMap":257},[262,701,702,704,707,710,712,715,718,720,722,724,726,728,730,732,735],{"class":264,"line":265},[262,703,269],{"class":268},[262,705,706],{"class":272}," record",[262,708,709],{"class":276}," LoginResult",[262,711,301],{"class":283},[262,713,714],{"class":293},"bool",[262,716,717],{"class":307}," Success",[262,719,311],{"class":283},[262,721,503],{"class":276},[262,723,506],{"class":283},[262,725,503],{"class":307},[262,727,311],{"class":283},[262,729,314],{"class":293},[262,731,506],{"class":283},[262,733,734],{"class":307}," Error",[262,736,354],{"class":283},[244,738,740],{"id":739},"why-it-pays-off","Why it pays off",[742,743,744,751,757],"ul",{},[745,746,747,750],"li",{},[239,748,749],{},"Reusable"," — the same service serves a console app, an API, and a background job.",[745,752,753,756],{},[239,754,755],{},"Testable"," — you assert on a returned value, not on captured output.",[745,758,759,762,763,766,767,769],{},[239,760,761],{},"Honest signatures"," — a method that returns ",[259,764,765],{},"User?"," tells you what it does; a ",[259,768,472],{}," that secretly prints does not.",[244,771,773],{"id":772},"its-the-same-rule-you-already-follow","It's the same rule you already follow",[235,775,776,777,780,781,784],{},"In Laravel you don't ",[259,778,779],{},"echo"," from a service — the service returns data and the\n",[239,782,783],{},"controller"," turns it into a response. Same instinct, same payoff. The console\nis just this project's \"view\".",[235,786,787,788,388],{},"See the C# services note for the full example in context → ",[789,790,791],"a",{"href":72},"Services & helpers",[793,794,795],"style",{},"html pre.shiki code .s5Kfy, html code.shiki .s5Kfy{--shiki-light:#9C3EDA;--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .s23sZ, html code.shiki .s23sZ{--shiki-light:#E2931D;--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .sD-vU, html code.shiki .sD-vU{--shiki-light:#E2931D;--shiki-default:#2E8F82;--shiki-dark:#5DA994}html pre.shiki code .soVBu, html code.shiki .soVBu{--shiki-light:#39ADB5;--shiki-default:#999999;--shiki-dark:#666666}html pre.shiki code .s8XtY, html code.shiki .s8XtY{--shiki-light:#39ADB5;--shiki-default:#1E754F;--shiki-dark:#4D9375}html pre.shiki code .sljsM, html code.shiki .sljsM{--shiki-light:#6182B8;--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .sYn-s, html code.shiki .sYn-s{--shiki-light:#E2931D;--shiki-default:#59873A;--shiki-dark:#80A665}html pre.shiki code .sSC40, html code.shiki .sSC40{--shiki-light:#90A4AE;--shiki-default:#B07D48;--shiki-dark:#BD976A}html pre.shiki code .siDh9, html code.shiki .siDh9{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#1E754F;--shiki-default-font-style:inherit;--shiki-dark:#4D9375;--shiki-dark-font-style:inherit}html pre.shiki code .sVsLi, html code.shiki .sVsLi{--shiki-light:#39ADB5;--shiki-default:#AB5959;--shiki-dark:#CB7676}html pre.shiki code .sbYkP, html code.shiki .sbYkP{--shiki-light:#39ADB5;--shiki-default:#B5695977;--shiki-dark:#C98A7D77}html pre.shiki code .sTbE_, html code.shiki .sTbE_{--shiki-light:#91B859;--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .su_V2, html code.shiki .su_V2{--shiki-light:#90A4AE;--shiki-default:#B56959;--shiki-dark:#C98A7D}html pre.shiki code .s9Tkl, html code.shiki .s9Tkl{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#A0ADA0;--shiki-default-font-style:inherit;--shiki-dark:#758575DD;--shiki-dark-font-style:inherit}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":257,"searchDepth":287,"depth":287,"links":797},[798,799,800,801],{"id":246,"depth":280,"text":247},{"id":475,"depth":280,"text":476},{"id":739,"depth":280,"text":740},{"id":772,"depth":280,"text":773},"2026-07-19","A service should return a result and let the caller decide how to show it — the single most useful separation-of-concerns rule.",false,"md",[807,808,809,810,811],"separation of concerns","service layer","presentation","return value","console.writeline",[],{},{"title":47,"description":803},[816,817,818,819],"clean-code","architecture","separation-of-concerns","services","lKGlk1bsLBcLs41BXKDbtzu83cZvcR1IZ0iicd0e6iQ",[822,824],{"title":43,"path":44,"stem":45,"description":823,"children":-1},"Practical rules for naming variables, functions and types so code reads like prose.",{"title":51,"path":52,"stem":53,"description":825,"children":-1},"Getting reliable, useful results out of language models.",[827,838,845,848,856,858,865,870,873,876,879,887,893,896,902,905,910,913,915,920,923,926,932,936,941,944,950,954,957,962,965,968,971,974,976,982,985,990,993,997,1000,1007,1012,1015,1021],{"path":135,"title":134,"description":828,"tags":829,"category":120,"featured":804,"updatedAt":836,"createdAt":836,"readingTime":837,"draft":804},"Teams of four build one frontend application over the whole semester using AI, in a single shared GitHub repository where everyone contributes.",[830,831,832,833,834,835],"teaching","activity","capstone","github","ai","frontend","2026-08-01",12,{"path":169,"title":168,"description":839,"tags":840,"category":162,"featured":804,"updatedAt":836,"createdAt":836,"readingTime":323,"draft":804},"Create one repository, push your project, and add your teammates as collaborators so the whole team can work on the same code.",[833,841,842,843,844],"git","collaboration","teamwork","setup",{"path":163,"title":162,"description":846,"tags":847,"category":162,"featured":804,"updatedAt":836,"createdAt":836,"readingTime":265,"draft":804},"Step-by-step references students can follow on their own during class.",[],{"path":159,"title":158,"description":849,"tags":850,"category":148,"featured":804,"updatedAt":855,"createdAt":855,"readingTime":287,"draft":804},"Install and configure the OpenCode CLI so AI writes code directly into your project folder instead of you copying it from a chat window.",[834,851,852,853,854],"opencode","cli","tooling","workflow","2026-07-25",{"path":48,"title":47,"description":803,"tags":857,"category":37,"featured":804,"updatedAt":802,"createdAt":802,"readingTime":280,"draft":804},[816,817,818,819],{"path":64,"title":63,"description":859,"tags":860,"category":864,"featured":804,"updatedAt":802,"createdAt":802,"readingTime":287,"draft":804},"Types, variables, control flow and how to write a function (method) in C#, mapped from PHP.",[256,861,862,863],"dotnet","basics","functions",null,{"path":68,"title":67,"description":866,"tags":867,"category":864,"featured":804,"updatedAt":802,"createdAt":802,"readingTime":287,"draft":804},"Create a class, give it properties and a constructor, and use it — the C# version of a Laravel model.",[256,861,868,869],"classes","models",{"path":72,"title":71,"description":871,"tags":872,"category":864,"featured":804,"updatedAt":802,"createdAt":802,"readingTime":287,"draft":804},"Put code in another file — a service or a static helper — and call it from anywhere, the C# way.",[256,861,819,817],{"path":76,"title":75,"description":874,"tags":875,"category":864,"featured":804,"updatedAt":802,"createdAt":802,"readingTime":287,"draft":804},"How C# files, namespaces, folders and the dotnet CLI fit together into a running app.",[256,861,853,817],{"path":58,"title":57,"description":877,"tags":878,"category":864,"featured":804,"updatedAt":802,"createdAt":802,"readingTime":265,"draft":804},"Learning C# and .NET from a Laravel\u002FPHP background — the fundamentals, mapped to what you already know.",[256,861,862],{"path":20,"title":19,"description":880,"tags":881,"category":13,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"The core Eloquent relationship types with examples and the N+1 trap to avoid.",[882,883,884,885],"laravel","eloquent","database","orm","2026-07-17",{"path":24,"title":23,"description":888,"tags":889,"category":13,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"My quick reference for setting up and using Laravel Homestead during development.",[882,890,891,892],"homestead","vagrant","virtualbox",{"path":14,"title":13,"description":894,"tags":895,"category":13,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"The PHP framework for web artisans — Eloquent, routing, queues and more.",[],{"path":34,"title":33,"description":897,"tags":898,"category":27,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":280,"draft":804},"Personal Bash aliases and helper functions that streamline my Laravel and Homestead development workflow.",[899,900,901,890,882],"bash","bashrc","git bash",{"path":28,"title":27,"description":903,"tags":904,"category":27,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"Shell scripting and a faster life on the command line.",[],{"path":44,"title":43,"description":823,"tags":906,"category":37,"featured":596,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},[816,907,908,909],"naming","readability","craft",{"path":38,"title":37,"description":911,"tags":912,"category":37,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"Writing code that other people (and future you) can read.",[],{"path":52,"title":51,"description":825,"tags":914,"category":51,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},[],{"path":86,"title":85,"description":916,"tags":917,"category":79,"featured":596,"updatedAt":886,"createdAt":886,"readingTime":280,"draft":804},"The Git commands I actually use day to day, plus how to undo things safely.",[841,852,918,919],"version-control","cheat-sheet",{"path":80,"title":79,"description":921,"tags":922,"category":79,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"Version control — everyday commands and getting out of trouble.",[],{"path":6,"title":10,"description":924,"tags":925,"category":10,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"Languages, frameworks and the craft of building software.",[],{"path":109,"title":108,"description":927,"tags":928,"category":89,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":323,"draft":804},"An introduction to AI-assisted software development, modern development workflows, and the mindset needed to use AI responsibly throughout the course.",[834,929,930,931],"software development","prompt engineering","beginners",{"path":113,"title":112,"description":933,"tags":934,"category":89,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":323,"draft":804},"Learn how to write effective AI prompts and read JavaScript code confidently. This lesson teaches students how to direct AI instead of blindly copying its output.",[834,935,930,931],"javascript",{"path":117,"title":116,"description":937,"tags":938,"category":89,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":287,"draft":804},"Introduction to the fundamentals of JavaScript, from variables and functions to loops, DOM manipulation, and events. This lesson prepares students for the Mini Product Store activity.",[935,931,939,940],"programming","web development",{"path":103,"title":102,"description":942,"tags":943,"category":102,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"Session-by-session plans with objectives, timing and materials.",[],{"path":127,"title":126,"description":945,"tags":946,"category":120,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":287,"draft":804},"Build a personal portfolio website using JavaScript arrays, loops, and DOM manipulation instead of hardcoded HTML.",[830,831,935,947,948,949],"portfolio","dom","loops",{"path":131,"title":130,"description":951,"tags":952,"category":120,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":287,"draft":804},"A JavaScript activity where students build a simple product store using arrays, loops, functions, DOM manipulation, and event listeners.",[830,831,935,948,953,949],"arrays",{"path":121,"title":120,"description":955,"tags":956,"category":120,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"In-class activities that get students doing, not just listening.",[],{"path":145,"title":144,"description":958,"tags":959,"category":120,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":280,"draft":804},"Practice reading JavaScript code, predicting its output, and verifying your understanding before running the program.",[830,831,935,863,960,961],"if-statement","debugging",{"path":139,"title":138,"description":963,"tags":964,"category":138,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"Practice problems that build skill through repetition.",[],{"path":155,"title":154,"description":966,"tags":967,"category":148,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":323,"draft":804},"A structured workflow for working with AI that emphasizes planning, review, and understanding before implementation.",[834,930,854],{"path":149,"title":148,"description":969,"tags":970,"category":148,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"Prompt templates for teaching with and about AI.",[],{"path":97,"title":96,"description":972,"tags":973,"category":89,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"Lesson plans, activities, rubrics and student references.",[],{"path":90,"title":89,"description":972,"tags":975,"category":89,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},[],{"path":180,"title":179,"description":977,"tags":978,"category":172,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"Two tiny functions to control how often a handler runs — with the difference explained.",[935,979,980,981],"performance","snippets","events",{"path":173,"title":172,"description":983,"tags":984,"category":172,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"Small, copy-ready fragments you reach for again and again.",[],{"path":190,"title":189,"description":986,"tags":987,"category":162,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"The essential Vim motions and commands to get productive and get out.",[988,989,919,852],"vim","editor",{"path":183,"title":162,"description":991,"tags":992,"category":162,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"Dense, scannable references for commands and syntax.",[],{"path":201,"title":200,"description":994,"tags":995,"category":193,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"A reusable PR description template that makes reviews faster and kinder.",[841,833,996,842],"template",{"path":194,"title":193,"description":998,"tags":999,"category":193,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"Reusable starting points for files, configs and docs.",[],{"path":212,"title":211,"description":1001,"tags":1002,"category":204,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":280,"draft":804},"The frontmatter and markdown conventions every note in this knowledge base follows.",[1003,1004,1005,1006],"meta","conventions","markdown","authoring",{"path":216,"title":215,"description":1008,"tags":1009,"category":204,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":280,"draft":804},"Every formatting feature this knowledge base supports, on one page — a living reference and rendering test.",[1005,1010,1011],"mdc","reference",{"path":205,"title":204,"description":1013,"tags":1014,"category":204,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"How this knowledge base itself is built and maintained.",[],{"path":227,"title":226,"description":1016,"tags":1017,"category":219,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"What a CORS error actually means and how to fix it — on the server, where it belongs.",[1018,1019,1020,961],"cors","http","apis",{"path":220,"title":219,"description":1022,"tags":1023,"category":219,"featured":804,"updatedAt":886,"createdAt":886,"readingTime":265,"draft":804},"Symptoms, causes and fixes for problems worth remembering.",[],1785598840912]