# The Stitching Beehive — Learning Guide Hey! This is the friendly companion to `TODO.md`. That file is written for the AI agent that's going to do the heavy lifting — it's terse and full of exact file paths and checks. **This** file is for you. It explains *why* each task exists, what you'll learn from it, and how the pieces fit together. The numbers here match the `TODO-NN` numbers over there. First, the big picture — and some genuine good news. You told me the site "displays inconsistently between my laptop and the server." That is a real, specific, fixable bug — not a sign you built the whole thing wrong. In fact, you already got a *lot* right: the project is cleanly separated into Controllers, Services, Models, and Views; you used dependency injection properly; you added security headers, response compression, and anti-forgery tokens; and you even wrote an HTML sanitizer because you correctly sensed that admin-entered content needs guarding. Those are instincts that take many people years to develop. What's tripping you up is a handful of specific issues, and once you see them named, none of them are mysterious. Two of them, in particular, explain almost everything you've been seeing. So let me start there before the task-by-task notes. ## The two bugs behind "it looks different on the server" **1. Every static file crashes in production (TODO-01).** Your code adds security headers in two places, and on the server those two places both try to add the *same* header to the same response. The method you used, `Headers.Add(...)`, throws an error if the header already exists (it's like `INSERT` failing on a duplicate key). On your laptop one of those two places is skipped (it only runs when *not* in Development mode), so you never see the crash. On the server, every request for a CSS file, a script, or an image throws — so the browser gets your page but none of your styling or images, and falls back to just the CDN styles. That's the "inconsistent appearance." The fix is one character per line: use `Headers["Name"] = value` instead of `Headers.Add(...)`. **Lesson:** "works on my machine" almost always means *something differs between the machines* — here, the Development vs. Production switch. Hunt for that difference. **2. The database is erased every time the app starts (TODO-02).** In `Program.cs` there's code that deletes `StitchingBeehive.db` on startup and rebuilds it from scratch. So every restart throws away everything — every tip, gallery photo, and contact message you'd added — and replaces it with placeholder seed data that points at images that don't even exist. Your laptop shows whatever you typed since its last restart; the server shows the placeholders. Same code, different "how long since it last restarted." **Lesson:** a database is supposed to *persist*. Deleting and recreating it is a thing people do once while prototyping and then forget to remove. We'll remove it now, and in Phase 4 replace it with "migrations," the proper way to evolve a database's shape over time without losing what's in it. Fix those two and the headline problem is gone. Everything else on the list is about making the project healthy, secure, and easy to deploy the same way every time — which is exactly what Docker gives us at the end. ## How the list is organized I split the work into phases that build on each other: - **Phase 1 — Critical fixes.** The two bugs above, plus the security and secret issues. Small, high-impact, do them first. - **Phase 2 — Cleanup.** Delete the confusing dead files so the repo stops looking like "a bit of a mess." Pure decluttering, no behavior change. - **Phase 3 — Platform port.** Move to .NET 10 and swap the image code for something that runs on Linux (this matters a lot for Docker — see below). - **Phase 4 — Database & content.** Adopt migrations and move your page content *into* the database instead of loose files. - **Phase 5 — Frontend.** Bundle Bootstrap/jQuery with the app instead of loading them from the internet at runtime. - **Phase 6 — Tooling.** Add automatic checks so problems get caught before they reach the server. - **Phase 7 — Docker.** The payoff: package everything so your laptop and the server run the *identical* thing. - **[HUMAN] items.** A couple of things only you can or should do. Work top to bottom. Each task in `TODO.md` lists what it depends on, so the order is safe. --- ## Phase 1 — Critical fixes **TODO-01 & TODO-02** — explained above. These are the two that fix your stated problem. **TODO-03: Replace the homemade admin login.** You built your own login using session variables and a "remember me" cookie. The instinct was right, but there are two holes. First, your security check (`CheckAuth()`) was written to *return* a "go to login" redirect — but the code that calls it on every save/delete action throws that redirect away and continues anyway. So right now, anyone on the internet can POST to your delete endpoints without logging in. Second, "remember me" just sets a cookie that literally says `AdminRememberMe=true`, which anyone can type into their browser to walk straight in. The fix is to use ASP.NET Core's built-in cookie authentication and put `[Authorize]` on the admin controller. **Lesson:** authentication is the classic "don't roll your own" area — the framework's version has had thousands of eyes on it. You keep your simple one-password setup; you just let the framework enforce it. As a bonus this deletes about 100 lines of copy-pasted checks. **TODO-04: Get passwords out of the source code.** Your admin password and your Gmail password are sitting in plain text in `appsettings.json`, which is committed to git. Anyone who sees the repo sees them. We'll move them to "environment variables" — values the server provides to the app at runtime, separate from the code. **Lesson:** secrets and code live in different places. Code is shared; secrets are not. (See also the [HUMAN] item — because these were committed, they need to be *changed*, not just moved.) **TODO-05: Use a real HTML sanitizer.** You wrote one with regular expressions to strip dangerous tags from uploaded content — good instinct, because that content gets rendered straight into the page. But sanitizing HTML with regex is famously unreliable (HTML is too irregular for it), and yours even has some stray non-English characters accidentally pasted into one of the patterns, so it doesn't catch what you think. We'll swap in a well-tested library that does this for a living. **Lesson:** for "parse this messy hostile format safely," reach for a maintained library, not hand-written pattern matching. --- ## Phase 2 — Cleanup **TODO-06: Delete the leftover website templates.** A big reason the repo "looks like a mess" is that it contains *two entire unrelated website templates* (one called CHEFER, one called Nexa) that you must have downloaded while looking for a design. They're about 11 MB of HTML, CSS, and JavaScript that your actual app never touches. Deleting them makes the project dramatically easier to understand. **Lesson:** dead code isn't harmless — it costs every future reader (including you in six months) time and confusion. Delete boldly; git remembers it if you ever want it back. **TODO-07: Stop committing the database and Word files to git.** Git is for *source* — the things you write. The `.db` file and the uploaded `.docx` files are *generated/runtime* data. The committed `.db` is actually empty anyway. **Lesson:** version control tracks the recipe, not the cake. **TODO-08: Remove a few unused bits of code.** A leftover field here, a method nobody calls there. Small, but it's the same principle as TODO-06 at a smaller scale. --- ## Phase 3 — Platform port **TODO-09: Replace the image code (this one matters most for Docker).** Your `ImageService` resizes and optimizes uploaded photos using `System.Drawing` — a graphics library that **only works on Windows**. On your Windows laptop it's fine. But Docker containers run Linux, and on Linux that code throws an error every time. Worse, you wrapped it in a "catch" that silently saves the *original, unoptimized* image when it fails — so on the server, image optimization has never actually run. We'll switch to ImageSharp, a library that works the same on every OS and needs nothing extra installed. **Lesson:** some libraries are OS-specific; if you ever plan to run on Linux (and Docker means you will), check that your dependencies are cross-platform. We do this *before* the .NET 10 upgrade so the project keeps building at every step. **TODO-10: Upgrade to .NET 10.** You're on .NET 6, which Microsoft stopped supporting in November 2024 (no more security patches). .NET 10 is the current long-term-support version, good through 2028. Because the code is small and uses mainstream features, this is mostly bumping version numbers. **Lesson:** frameworks have support lifecycles like milk has expiration dates; staying on a supported one is part of basic maintenance. **TODO-11: Make thumbnails actually work.** Your gallery page asks for a small "thumbnail" version of each image, and you even wrote the code to *generate* thumbnails — but nothing ever calls it, so the gallery quietly loads full-size photos and shrinks them in the browser (slow). We'll connect the two. **Lesson:** a feature isn't done until something actually calls it; "the function exists" and "the function runs" are different things. --- ## Phase 4 — Database & content **TODO-12: Adopt migrations.** This is the proper replacement for the "delete and recreate the database" hack from TODO-02. A *migration* is a recorded, versioned change to your database's structure — add a column, create a table — that applies cleanly to an existing database without wiping it. **Lesson:** this is how real apps evolve their data shape over months and years without data loss. It's one of the most useful concepts to internalize. **TODO-13: Delete the placeholder content.** The seed data invents three tips, three services, and four gallery items that point at images and files that don't exist. With the database no longer being wiped, this fake data would just clutter the real site. We keep the genuinely-static reference data (your social-media links). **Lesson:** "seed data" should be real defaults, not leftover scaffolding. **TODO-14 & TODO-15: Move page content into the database.** Right now your tips and services are stored as Word `.docx` files, and the app re-converts them to HTML *on every single page view*. Your homepage/about/contact text lives in loose `.txt` files. We'll convert each Word doc to HTML once, when you upload it, and store the result in the database — and do the same for the site text. This is faster, fixes a bug where images in a doc came out duplicated, and stops your raw Word files from being publicly downloadable. **Lesson:** do expensive work once and store the result, rather than redoing it on every request. And keeping content in the database (instead of scattered files) means there's *one* place to back up and move. **TODO-16: Tidy the database rules.** A few small correctness fixes: make sure there can't be two "Home" image slots, cap the contact-message length so nobody can submit a megabyte of text, and stop declaring the same validation rule in two places. **Lesson:** constraints belong in the database/model, declared once. **TODO-17: Put your data in one external folder.** Today your database and uploaded images live *inside* the app's folder. That's why deploying could wipe them — the old FTP setup was even configured to delete existing files on publish! We'll move all the changeable data into a single `App_Data` folder whose location is configurable. **Lesson:** separate the *application* (which you replace on every deploy) from the *data* (which must survive deploys). This separation is exactly what makes the Docker step clean. --- ## Phase 5 — Frontend **TODO-18: Bundle Bootstrap and friends with the app.** Your pages currently download Bootstrap, jQuery, and similar from public internet CDNs each time. Two problems: your public site and your admin pages load *different versions* of Bootstrap (a subtle source of "looks different"), and the site breaks if those CDNs are slow or blocked. We'll download fixed versions into the project. Earlier you moved to CDNs to shrink the upload to the server — but inside Docker that concern disappears, so local files are the better trade. **Lesson:** depending on someone else's server at runtime is a reliability and consistency risk; pin your dependencies. --- ## Phase 6 — Tooling **TODO-19 & TODO-20: Add automatic checks.** We'll turn on the compiler's built-in code analyzers and add a "CI" workflow — a script GitHub runs automatically on every change that builds the project on a clean Linux machine and flags problems. **Lesson:** this is the deepest cure for "works on my machine." CI builds on a *fresh* computer that has none of your laptop's hidden setup, so if it passes there, it'll run on the server. Let the robot catch the boring mistakes so you don't have to. --- ## Phase 7 — Docker (the payoff) **TODO-21: Containerize it.** Now that the app is cross-platform (Phase 3) and its data lives in one external folder (TODO-17), we package the whole thing into a Docker image: a sealed box containing the exact .NET runtime, your app, and its files. Your laptop and the server both run that *same box*, so "it works differently over there" stops being possible — they're no longer different environments, they're the same image. The database and uploads live in a Docker "volume" (a folder that survives rebuilds), so deploying a new version never touches your data. **Lesson:** this is the whole idea behind containers — "build once, run anywhere, identically." Notice we did it *last*: Docker can only deliver consistency once the app itself is consistent and portable. If we'd started here, we'd just have shipped the bugs in a nicer box. --- ## The two things only you can do **TODO-H1: Change the leaked passwords.** Your Gmail and admin passwords were committed to git, so moving them to environment variables (TODO-04) isn't enough — they've been exposed and must be *changed*. Revoke the old Gmail "app password" in your Google account, pick a new admin password, and hand both to the server as environment variables. **Lesson:** once a secret has been committed, treat it as compromised forever. Rotating it is the only real fix. **TODO-H2: Decide whether to scrub git history.** The old passwords still sit in past commits. For a small private project, changing them (H1) is normally enough — the old ones no longer work, so who cares if they're visible. If you want them physically removed from history anyway, that's a more involved, irreversible operation best done carefully by a human, not an automated agent. --- You've got this. None of these are "you did it wrong" — they're "here's the next thing to learn," and they're in the order that keeps the project working at every step. Tackle them one at a time, check each one off only when its `TODO.md` "Done when" boxes pass, and by the end you'll have a site that deploys the same way every time and a real mental model of why. --- # Part 2 — Postscript: lessons from the deployment review This section was added later, after the TODO list above was (impressively!) almost entirely done and the project was reviewed end-to-end for deployment. The review found a handful of new issues — and they're worth more than the originals, because every one of them crept in *during* the fixing. That's not a failure; it's the normal second act of every real project, and each one teaches something the first list couldn't. **P1. The site you run is not the site you ship.** After the frontend libraries were vendored back into `wwwroot/lib` (TODO-18), the project file still contained three `` lines left over from the abandoned go-use-CDNs approach. Result: `dotnet run` looked perfect, CI was green, and yet every *published* copy of the site — Docker image or FTP — silently shipped with no Bootstrap, no Font Awesome, no jQuery. Three different things can each claim "it works": the dev server (`dotnet run`), the build (CI), and the published artifact (`dotnet publish`). They overlap a lot, but the bugs live in the gaps. **Lesson one:** when you reverse a decision, hunt down every trace of the old one — settings files, project files, and docs all keep fossils. **Lesson two:** before calling a deployment story done, run the *published artifact* (here: build the Docker image and click around in it), not just the dev server. **P2. The password that came back.** A commit titled "Update admin password to AdminDebbie2025" re-inserted the admin password into `appsettings.json` — the *same* password that had been leaked, which TODO-04 had deliberately blanked and TODO-H1 said to retire. Almost certainly what happened: login stopped working (because an empty configured password now fails closed — that's the safety feature doing its job), and putting the old value back was the fastest visible fix. **Lesson:** when something "breaks" right after a deliberate change, read the commit message and the TODO item behind it before undoing it. The inconvenience is often the security working. The intended fix was one line in a `.env` file (`ADMIN_PASSWORD=...`) — same effort, no secret in git. And a secret that has ever been committed stays burned forever; re-committing it resets the clock on nothing and re-confirms the old value to anyone reading history. **P3. Your IDE's generated files are suggestions, not decisions.** Visual Studio's container tooling generated its own Dockerfiles and, in the shuffle, the purpose-built Linux production Dockerfile got pushed aside into a `.original` file while a **Windows-container** Dockerfile took its place — one that can never build or run on a Linux server, and that dropped the `/data` setting, so the database volume would have been silently ignored. **Lesson:** when a tool offers to generate a file that already exists, that's a merge to review, not a prompt to click through. Generated code is a starting point written by something that doesn't know your deployment target. The repo now keeps both on purpose — `Dockerfile` for VS debugging on Windows, `Dockerfile.linux` for the server — with `docker-compose.yml` pinned explicitly to the Linux one. Naming the files for their *purpose* is what makes that arrangement stable. **P4. Downgrade the tool, not the product.** The project was moved .NET 10 → 9 because the installed Visual Studio couldn't open .NET 10 — and .NET 9, an 18-month "STS" release, left support in May 2026, right before launch. The app still runs fine in Docker on Linux, but it no longer receives security patches. **Lesson:** when your tooling can't handle the current platform, the durable fix is updating the tooling; downgrading the product trades a ten-minute install for a supportability problem that ships to production. Before pinning any version, check its support window (LTS = 3 years; STS = 18 months) against your launch date. **P5. Dead files talk — and now machines are listening.** The cleanup round removed a second abandoned Dockerfile, dead FTP scripts, and an orphaned `wwwroot/content` folder whose old text files were still being served publicly to anyone who guessed the URL. The original TODO-06 lesson said dead code costs every future *reader*. There's a new reader now: AI agents treat whatever is in the repo as ground truth. A stale Dockerfile isn't just clutter — it's an instruction you didn't mean to give, to a collaborator that takes everything literally. A clean repo isn't tidiness anymore; it's prompt engineering. **P6. Working with agents: what went right, and the two habits that matter.** It's worth saying plainly — the agent-driven run through this TODO list *worked*. Twenty-odd items landed, CI came up green, and the verification steps ("Done when") are why: an agent given a checkable goal can confirm its own work, and so can you, without trusting anyone's memory. Keep writing acceptance criteria like that for any task you hand off, to a human or a machine. The two regressions above (P2, P3) share a root cause worth noticing: both were *manual overrides of deliberate agent work, made without first reading why the work was done that way*. So, two habits: **(1)** before manually undoing or "fixing" something an agent did, read its commit message — and if it still seems wrong, ask the agent itself to explain or to make your change, so the intent stays recorded; **(2)** after you hand-edit something in an agent-managed area, say so in your next session — agents, like teammates, make bad assumptions when the repo changed under them and nobody mentioned it. The same courtesy you'd give a human collaborator turns out to be exactly what makes the machine ones effective.