[{"data":1,"prerenderedAt":755},["ShallowReactive",2],{"projects:list":3},[4,130,285,404,509,605],{"id":5,"title":6,"body":7,"demo":93,"description":94,"docs":95,"draft":96,"extension":97,"highlights":98,"image":108,"language":109,"meta":110,"navigation":111,"order":112,"path":113,"repo":114,"seo":115,"site":93,"stack":116,"stem":123,"tagline":124,"tags":125,"__hash__":129},"projects\u002Fprojects\u002Fforge.md","forge",{"type":8,"value":9,"toc":86},"minimark",[10,15,19,27,31,39,42,68,72],[11,12,14],"h2",{"id":13},"the-problem-it-solves","The problem it solves",[16,17,18],"p",{},"If your PHP toolchain lives inside a container, every mainstream hook runner\nmakes you write a wrapper script to shell into it. Husky and lint-staged assume\nNode on the host. lefthook is a single binary, but it still runs tools where it\nhappens to be, not where they are installed.",[16,20,21,22,26],{},"forge routes each tool to the container for you. Set ",[23,24,25],"code",{},"backend = \"ddev\""," (or a\ncontainer name), and it detects the running environment and executes the tool\nthere. If the container is not up, it falls back to the host rather than failing\nthe commit.",[11,28,30],{"id":29},"why-it-is-on-this-page","Why it is on this page",[16,32,33,34,38],{},"This is the project where the test suite matters most, because a hook runner\nthat misbehaves blocks every commit on the team. ",[35,36,37],"strong",{},"297 test functions across 22\ntest files"," cover config parsing, staged-file filtering, container detection,\nand the commit-message policy engine.",[16,40,41],{},"The design constraints were deliberate:",[43,44,45,56,62],"ul",{},[46,47,48,51,52,55],"li",{},[35,49,50],{},"A single static binary."," A tool that enforces quality gates cannot itself\nrequire a package manager to install. ",[23,53,54],{},"go install",", Homebrew tap, or a curl\ninstaller.",[46,57,58,61],{},[35,59,60],{},"TOML config, not shell."," The config file is data, so it can be validated\nand diffed, rather than a script that can do anything.",[46,63,64,67],{},[35,65,66],{},"Graceful degradation."," Container missing, tool missing, no staged files -\neach has a defined behavior instead of an unhandled error.",[11,69,71],{"id":70},"what-is-inside","What is inside",[43,73,74,77,80,83],{},[46,75,76],{},"Commit-message policy (conventional commits) built into the runner, so\ncommitlint and its Node dependency are not needed",[46,78,79],{},"Monorepo workspace mode, staged-file filtering, parallel execution",[46,81,82],{},"A migration path from Husky that reads the existing config",[46,84,85],{},"Documentation site, changelog automation via release-please, and a demo\nrecorded with VHS",{"title":87,"searchDepth":88,"depth":88,"links":89},"",2,[90,91,92],{"id":13,"depth":88,"text":14},{"id":29,"depth":88,"text":30},{"id":70,"depth":88,"text":71},null,"A git hook runner that executes your linters and formatters inside DDEV or Docker, where the tools actually live. One Go binary, no Node.js, no wrapper scripts.","https:\u002F\u002Fterrorsquad.github.io\u002Fforge\u002F",false,"md",[99,102,105],{"value":100,"label":101},"297","Go test functions across 22 test files",{"value":103,"label":104},"1","static binary, zero runtime dependencies",{"value":106,"label":107},"5","CI workflows, including an install smoke test","\u002Fprojects\u002Fforge.webp","Go",{},true,1,"\u002Fprojects\u002Fforge","https:\u002F\u002Fgithub.com\u002FTerrorSquad\u002Fforge",{"title":6,"description":94},[109,117,118,119,120,121,122],"Cobra","TOML","DDEV","Docker","GitHub Actions","Homebrew","projects\u002Fforge","The git hook runner for containerized dev",[126,127,109,128],"Developer Tooling","CLI","Testing","C3w1BgwjmaLfFBPKqedb_gXMWBHkI7slEWfyWq5w7Zw",{"id":131,"title":132,"body":133,"demo":93,"description":251,"docs":252,"draft":96,"extension":97,"highlights":253,"image":263,"language":264,"meta":265,"navigation":111,"order":88,"path":266,"repo":267,"seo":268,"site":93,"stack":269,"stem":277,"tagline":278,"tags":279,"__hash__":284},"projects\u002Fprojects\u002Fgstack.md","GStack",{"type":8,"value":134,"toc":246},[135,139,158,164,168,206,209,213],[11,136,138],{"id":137},"the-architectural-idea","The architectural idea",[16,140,141,142,145,146,149,150,153,154,157],{},"Most SaaS starters put tenancy in the application layer: a ",[23,143,144],{},"where tenant_id = ?","\nthat someone eventually forgets. GStack pushes it into Postgres. Every table is\ntenant-scoped by row-level security through a ",[23,147,148],{},"current_tenant_id()"," helper, so a\nmissing filter in application code is not a data leak. Roles (",[23,151,152],{},"member"," \u002F\n",[23,155,156],{},"admin",") are enforced the same way, with page metadata handling UX only.",[16,159,160,161],{},"That is the distinction I care about: ",[35,162,163],{},"the database is the gate, the UI is the\nconvenience.",[11,165,167],{"id":166},"feature-layers","Feature layers",[16,169,170,171,174,175,174,177,180,181,174,184,174,187,174,190,174,193,174,196,174,199,202,203,205],{},"The codebase is split into ten Nuxt layers - ",[23,172,173],{},"account",", ",[23,176,156],{},[23,178,179],{},"analytics",",\n",[23,182,183],{},"billing",[23,185,186],{},"email",[23,188,189],{},"feedback",[23,191,192],{},"marketing",[23,194,195],{},"notes",[23,197,198],{},"tour",[23,200,201],{},"ui",". Each owns\nits pages, composables, and types, and most can be switched off with an\nenvironment variable. ",[23,204,195],{}," is a deliberately boring CRUD example that shows\nthe pattern to copy.",[16,207,208],{},"Layers are not decoration here. They mean a feature can be deleted without\ngrepping the whole app, and a new domain can be added without touching existing\nones.",[11,210,212],{"id":211},"operational-maturity","Operational maturity",[43,214,215,225,231,237],{},[46,216,217,220,221,224],{},[35,218,219],{},"Billing"," via Polar checkout, customer portal, and webhook, with plan gating\nthrough a ",[23,222,223],{},"useSubscription()"," composable",[46,226,227,230],{},[35,228,229],{},"Notifications"," - a database trigger fans an event out to tenant admins, a\nwebhook mirrors it to transactional email",[46,232,233,236],{},[35,234,235],{},"Observability"," - Sentry on client and server, PostHog analytics, and\nfeature flags that degrade safely when the service is unreachable",[46,238,239,242,243],{},[35,240,241],{},"CI"," - lint, typecheck, tests, plus dedicated accessibility and screenshot\nworkflows; screenshots in the README regenerate with ",[23,244,245],{},"pnpm screenshots",{"title":87,"searchDepth":88,"depth":88,"links":247},[248,249,250],{"id":137,"depth":88,"text":138},{"id":166,"depth":88,"text":167},{"id":211,"depth":88,"text":212},"A production-ready starting point for multi-tenant SaaS: Nuxt 4, Supabase, row-level-security tenancy, billing, notifications, and observability already wired together and tested.","https:\u002F\u002Fterrorsquad.github.io\u002Fgstack\u002F",[254,257,260],{"value":255,"label":256},"10","isolated Nuxt layers, each a self-contained feature",{"value":258,"label":259},"RLS","tenancy enforced in the database, not app code",{"value":261,"label":262},"19","test files, plus automated a11y and screenshot CI","\u002Fprojects\u002Fgstack.webp","TypeScript",{},"\u002Fprojects\u002Fgstack","https:\u002F\u002Fgithub.com\u002FTerrorSquad\u002Fgstack",{"title":132,"description":251},[270,264,271,272,273,274,275,276],"Nuxt 4","Supabase","PostgreSQL","Nuxt UI v4","Playwright","Polar","Resend","projects\u002Fgstack","Multi-tenant SaaS starter, batteries included",[280,281,282,283],"Architecture","SaaS","Multi-tenancy","Nuxt","CFRvvIXS0QVIUtEgtXfJ97uMOYAUnsPG8edFdRVlPxI",{"id":286,"title":287,"body":288,"demo":93,"description":379,"docs":93,"draft":96,"extension":97,"highlights":380,"image":390,"language":264,"meta":391,"navigation":111,"order":392,"path":393,"repo":93,"seo":394,"site":299,"stack":395,"stem":398,"tagline":399,"tags":400,"__hash__":403},"projects\u002Fprojects\u002Fmoj-go.md","moj·GO",{"type":8,"value":289,"toc":374},[290,304,308,311,317,321,327,330,334],[291,292,293],"blockquote",{},[16,294,295,296,303],{},"This one is a commercial product running at ",[297,298,302],"a",{"href":299,"rel":300},"https:\u002F\u002Fmojgo.rs",[301],"nofollow","mojgo.rs",", so\nthe repository is private. The numbers below are from the codebase itself, and\nI am happy to walk through the architecture or the test suite on a call.",[11,305,307],{"id":306},"why-this-one-is-the-real-test","Why this one is the real test",[16,309,310],{},"A leave tracker sounds simple until you write down the rules. Entitlement\naccrues over a year. Unused days carry over, but only some of them, and only\nuntil a cutoff. Public holidays are national but companies add their own. A\nrequest spans a weekend differently than a Tuesday. Approvals route to a manager\nwho may themselves be on leave.",[16,312,313,314],{},"Getting that wrong means someone loses days they earned. ",[35,315,316],{},"This is the kind of\ndomain where the tests are not ceremony.",[11,318,320],{"id":319},"how-it-is-tested","How it is tested",[16,322,323,326],{},[35,324,325],{},"43 end-to-end Playwright specs and 28 unit test files."," The end-to-end weight\nis deliberate: the risky behavior in this app is multi-step and role-dependent -\nan employee submits, an admin approves, a balance changes - and that only holds\ntogether in a real browser against a real database.",[16,328,329],{},"The e2e suite includes visual snapshot tests across desktop and mobile, so a\nlayout regression fails CI rather than reaching a user.",[11,331,333],{"id":332},"engineering-practice-on-display","Engineering practice on display",[43,335,336,346,352,362,368],{},[46,337,338,341,342,345],{},[35,339,340],{},"81 SQL migrations."," Every schema change is a reviewable, replayable file.\nTypes are regenerated from the live schema (",[23,343,344],{},"pnpm db:types","), so the database\nis the source of truth for TypeScript, not a hand-maintained parallel model.",[46,347,348,351],{},[35,349,350],{},"150 markdown documents"," - architecture decision records, runbooks, email\ntemplate references. The decisions are written down where the next person\nlooks.",[46,353,354,357,358,361],{},[35,355,356],{},"Seeded demo data"," - realistic tenants, employees, and requests, all tagged\n",[23,359,360],{},"is_demo",", so the app can be demonstrated and tested without touching real\nrecords.",[46,363,364,367],{},[35,365,366],{},"Bilingual"," (Serbian \u002F English) from the start, because retrofitting i18n is\nhow you learn not to retrofit i18n.",[46,369,370,373],{},[35,371,372],{},"Sentry"," on client and server, plus accessibility and screenshot CI\nworkflows.",{"title":87,"searchDepth":88,"depth":88,"links":375},[376,377,378],{"id":306,"depth":88,"text":307},{"id":319,"depth":88,"text":320},{"id":332,"depth":88,"text":333},"A multi-tenant HR leave-management app for Serbian companies. Real product, real domain rules: accrual, carryover, public holidays, and an approval chain that has to be right.",[381,384,387],{"value":382,"label":383},"71","test files - 43 end-to-end specs, 28 unit",{"value":385,"label":386},"81","SQL migrations, schema versioned from day one",{"value":388,"label":389},"150","markdown docs: ADRs, runbooks, references","\u002Fprojects\u002Fmoj-go.webp",{},3,"\u002Fprojects\u002Fmoj-go",{"title":287,"description":379},[270,264,271,272,396,274,372,397],"Pinia","i18n","projects\u002Fmoj-go","Multi-tenant HR leave management, in production",[401,282,128,402],"Product","Domain Modelling","dSJd7m7u65RcRKhq2DbQPkw-7OEB5fD_3QddkuMlIjs",{"id":405,"title":406,"body":407,"demo":93,"description":479,"docs":480,"draft":96,"extension":97,"highlights":481,"image":93,"language":489,"meta":490,"navigation":111,"order":491,"path":492,"repo":493,"seo":494,"site":93,"stack":495,"stem":502,"tagline":503,"tags":504,"__hash__":508},"projects\u002Fprojects\u002Fphp-booster.md","Booster",{"type":8,"value":408,"toc":474},[409,413,416,419,423,461,465,471],[11,410,412],{"id":411},"the-argument","The argument",[16,414,415],{},"Every team relitigates the same setup: which static analyzer, at what level,\nwhich formatter, which git hooks, and who fixes the 400 pre-existing violations.\nThe discussion costs more than the decision. Booster is that decision made once,\nwith the configuration that makes it survive contact with an existing codebase.",[16,417,418],{},"One command adds the whole stack to a project, with an interactive wizard for\nchoosing which tools you actually want.",[11,420,422],{"id":421},"what-it-installs","What it installs",[43,424,425,431,437,443,449,455],{},[46,426,427,430],{},[35,428,429],{},"Static analysis"," - PHPStan and Psalm, configured at a level a real\ncodebase can adopt rather than the maximum that forces a mass baseline",[46,432,433,436],{},[35,434,435],{},"Formatting"," - EasyCodingStandard, applied automatically",[46,438,439,442],{},[35,440,441],{},"Refactoring"," - Rector, for mechanical language-version upgrades",[46,444,445,448],{},[35,446,447],{},"Git hooks"," - quality gates before the commit, not after the review",[46,450,451,454],{},[35,452,453],{},"IDE config"," - VS Code and PhpStorm settings, so the editor agrees with CI",[46,456,457,460],{},[35,458,459],{},"DDEV integration"," - optional, but the tools run inside the container when\nit is there",[11,462,464],{"id":463},"the-part-i-think-is-right","The part I think is right",[16,466,467,470],{},[35,468,469],{},"Formatting and modernization are fixed in CI, not in code review."," A GitHub\nAction applies the automated fixes and pushes them back. Style is never a review\ncomment, because a human arguing about brace placement is a human not reviewing\nthe logic.",[16,472,473],{},"The bundle versions itself, updates its own dependencies through a scheduled\nworkflow, and ships a documentation site. Integration tests verify the installer\nactually works against a fresh project, which is the test that matters for\nsomething whose entire job is installation.",{"title":87,"searchDepth":88,"depth":88,"links":475},[476,477,478],{"id":411,"depth":88,"text":412},{"id":421,"depth":88,"text":422},{"id":463,"depth":88,"text":464},"The PHP quality toolchain I bring to a team, packaged as one installable bundle: PHPStan, Psalm, Rector, ECS, git hooks, CI, and IDE config, pre-wired and ready on day one.","https:\u002F\u002Fterrorsquad.github.io\u002Fphp-booster\u002F",[482,484,487],{"value":103,"label":483},"command to add the full toolchain to a project",{"value":485,"label":486},"Auto","formatting and modernization fixed in CI, not review",{"value":106,"label":488},"CI workflows, including integration tests","PHP \u002F Shell",{},4,"\u002Fprojects\u002Fphp-booster","https:\u002F\u002Fgithub.com\u002FTerrorSquad\u002Fphp-booster",{"title":406,"description":479},[496,497,498,499,500,119,121,501],"PHP","PHPStan","Psalm","Rector","ECS","Python","projects\u002Fphp-booster","A PHP quality toolchain, packaged",[505,506,496,507],"Developer Experience","Standards","Automation","h1tQbiBW43kLeOk-OKxZVQn9MEMyzJy9mKyg94KUG1M",{"id":510,"title":511,"body":512,"demo":93,"description":575,"docs":576,"draft":96,"extension":97,"highlights":577,"image":93,"language":587,"meta":588,"navigation":111,"order":589,"path":590,"repo":591,"seo":592,"site":93,"stack":593,"stem":599,"tagline":600,"tags":601,"__hash__":604},"projects\u002Fprojects\u002Fgriffin.md","Griffin",{"type":8,"value":513,"toc":570},[514,518,525,528,532,538,549,553],[11,515,517],{"id":516},"the-premise","The premise",[16,519,520,521,524],{},"A development machine accumulates state. Six months of ",[23,522,523],{},"apt install",", a dotfile\nedited at 1am, a PATH entry nobody remembers adding. Then the laptop dies, or a\nnew person joins, and the setup is archaeology.",[16,526,527],{},"Griffin makes the workstation reproducible: the machine is described in version\ncontrol, and rebuilding it is a command rather than a weekend.",[11,529,531],{"id":530},"why-it-earns-its-place-here","Why it earns its place here",[16,533,534,537],{},[35,535,536],{},"It is tested on three platforms in continuous integration."," Ubuntu, WSL, and\nmacOS each have their own workflow that runs the playbook on a clean runner. An\nAnsible role that works on the author's machine is a script; one that provisions\na fresh Ubuntu runner, a WSL environment, and both Intel and Apple Silicon macOS\nis infrastructure.",[16,539,540,541,544,545,548],{},"That is also the hard part. The same intent - install this tool, set this\npreference - is ",[23,542,543],{},"apt"," here, ",[23,546,547],{},"homebrew"," there, and a registry-adjacent quirk in\nWSL. The playbooks abstract the intent and branch on the platform, rather than\nforking into three copies that drift.",[11,550,552],{"id":551},"structure","Structure",[43,554,555,561,564,567],{},[46,556,557,560],{},[35,558,559],{},"65 YAML task files",", organized by concern, each idempotent, so re-running\nconverges rather than duplicating",[46,562,563],{},"Separate playbooks for Linux and macOS sharing common roles",[46,565,566],{},"Configuration import for KDE and editor settings",[46,568,569],{},"Linting via reviewdog in CI, documentation built and published automatically,\nreleases managed by release-please",{"title":87,"searchDepth":88,"depth":88,"links":571},[572,573,574],{"id":516,"depth":88,"text":517},{"id":530,"depth":88,"text":531},{"id":551,"depth":88,"text":552},"Ansible playbooks that take a bare Linux or macOS machine to a fully configured development workstation, verified on every push across Ubuntu, WSL, and macOS runners.","https:\u002F\u002Fterrorsquad.github.io\u002Fansible-post-installation\u002F",[578,581,584],{"value":579,"label":580},"3","platforms tested in CI: Ubuntu, WSL, macOS",{"value":582,"label":583},"65","YAML task files, idempotent by construction",{"value":585,"label":586},"0","manual steps to rebuild a machine","Ansible \u002F YAML",{},5,"\u002Fprojects\u002Fgriffin","https:\u002F\u002Fgithub.com\u002FTerrorSquad\u002Fansible-post-installation",{"title":511,"description":575},[594,595,596,597,121,122,598],"Ansible","YAML","Bash","Zsh","WSL","projects\u002Fgriffin","A dev workstation from bare metal, reproducibly",[602,507,603,241],"Infrastructure","Cross-platform","VOJPWWFenOl09nRglF71orFSdXYzJFBJKtYZ68jvSqw",{"id":606,"title":607,"body":608,"demo":728,"description":729,"docs":93,"draft":96,"extension":97,"highlights":730,"image":740,"language":264,"meta":741,"navigation":111,"order":742,"path":743,"repo":744,"seo":745,"site":93,"stack":746,"stem":749,"tagline":750,"tags":751,"__hash__":754},"projects\u002Fprojects\u002Fsudoku.md","Sudoku Pro",{"type":8,"value":609,"toc":723},[610,614,617,625,632,636,639,713,716,720],[11,611,613],{"id":612},"the-interesting-problem","The interesting problem",[16,615,616],{},"Writing a Sudoku solver is a known exercise: backtracking search finishes in\nmilliseconds. But a solver that brute-forces the answer cannot teach anyone\nanything, because \"the answer is 7 because I tried every branch\" is not a\nreason.",[16,618,619,620,624],{},"So the engine here solves the way a person does. It models candidates per cell\nand applies human techniques in ascending order of difficulty - naked single,\nhidden single, then pointing pairs, box\u002Fline reduction, and further - returning\nthe ",[621,622,623],"em",{},"simplest"," technique that makes progress on the current board, along with\nthe cells that justify it.",[16,626,627,628,631],{},"That constraint, ",[35,629,630],{},"explainability over speed",", is what makes the design\nnon-trivial. The hint system and the difficulty grader fall out of it: a puzzle's\nrating is the hardest technique required to solve it without guessing.",[11,633,635],{"id":634},"where-the-tests-are","Where the tests are",[16,637,638],{},"The logic is separated from the UI precisely so it can be tested as logic. Six\nunit-tested modules cover the core:",[640,641,642,655],"table",{},[643,644,645],"thead",{},[646,647,648,652],"tr",{},[649,650,651],"th",{},"Module",[649,653,654],{},"What it proves",[656,657,658,673,683,693,703],"tbody",{},[646,659,660,670],{},[661,662,663,666,667],"td",{},[23,664,665],{},"sudokuCore"," \u002F ",[23,668,669],{},"sudokuEngine",[661,671,672],{},"Constraint propagation and validity",[646,674,675,680],{},[661,676,677],{},[23,678,679],{},"sudokuGrader",[661,681,682],{},"Difficulty rating matches required technique",[646,684,685,690],{},[661,686,687],{},[23,688,689],{},"nextHint",[661,691,692],{},"The right technique is chosen, with correct justification",[646,694,695,700],{},[661,696,697],{},[23,698,699],{},"score",[661,701,702],{},"Scoring and streak rules",[646,704,705,710],{},[661,706,707],{},[23,708,709],{},"dailyPuzzle",[661,711,712],{},"Deterministic daily generation",[16,714,715],{},"Four Playwright specs cover the parts unit tests cannot: game flow, pausing,\npreferences, and resuming an interrupted session.",[11,717,719],{"id":718},"the-rest","The rest",[16,721,722],{},"Installable PWA that works offline, keyboard-driven controls, pencil-mark notes\nwith auto-fill, undo, and a bilingual interface. Deployed to GitHub Pages on\nevery push.",{"title":87,"searchDepth":88,"depth":88,"links":724},[725,726,727],{"id":612,"depth":88,"text":613},{"id":634,"depth":88,"text":635},{"id":718,"depth":88,"text":719},"https:\u002F\u002Fterrorsquad.github.io\u002Fsudoku\u002F","A Sudoku game that teaches you to solve Sudoku. A constraint-propagation engine detects which human technique applies to the current board and walks through the reasoning, step by step.",[731,734,737],{"value":732,"label":733},"6","unit-tested modules: engine, grader, hints, scoring",{"value":735,"label":736},"8+","solving techniques, from naked single upward",{"value":738,"label":739},"PWA","installable, works offline","\u002Fprojects\u002Fsudoku.webp",{},6,"\u002Fprojects\u002Fsudoku","https:\u002F\u002Fgithub.com\u002FTerrorSquad\u002Fsudoku",{"title":607,"description":729},[270,747,264,748,274,738],"Vue 3","Vitest","projects\u002Fsudoku","A solver that explains itself",[752,264,128,753],"Algorithms","Vue","y6oALmqADy3WmQ6gRGDNv76FOBbn-H-OB8OArn-RVrQ",1785847685792]