Projects

Things I've built end to end — scoped, secured, deployed and maintained. Written up as case studies rather than screenshots, because the decisions and the verification are the interesting part.

Live 2026

Portfolio Site

This site. A statically generated professional presence, built from scratch and hardened to a defined security standard.

Role
Sole engineer — design, build, security and deployment
Stack
HTML5 · CSS3 · vanilla JavaScript · Git · Vercel
Timeline
Specification to live production in a single day

The brief

I needed a professional presence I actually control — somewhere that says what I do before an interview or a conversation, rather than leaving it to a social profile I don't own. The second goal was deliberate: I wanted to learn modern web delivery properly, from source file to live domain, by doing it rather than reading about it.

The requirements were pinned down in writing before a line of code was committed — audience, must-have versus nice-to-have, what the site deliberately would not do, and how it would be hosted.

Key decisions

  • Static, not dynamic. Nothing here needs a server to think. Every page is written in advance and served unchanged, which removes the database, the session layer and the injection surface entirely. The cheapest vulnerability to fix is the one you never build.
  • No framework. A portfolio has almost no state, so a component library would have added a build system and an abstraction layer for no functional gain — and obscured what the browser is actually doing. Plain HTML, CSS and JavaScript, deliberately.
  • Zero runtime dependencies. No third-party packages ship to production. That means no dependency vulnerabilities, no supply-chain exposure, and nothing to patch on somebody else's release schedule.
  • Own the pipeline. A hosted site builder would have been faster to stand up but would have meant no control over security headers and no way to leave. Owning the repository keeps both.
  • Ship a small scope well. Features that could not be done properly were cut rather than half-built, and recorded as deferred with the reasoning attached.

How it's built

  • Semantic, accessible markup — skip navigation, managed focus states, ARIA state kept in sync with what's on screen, and reduced-motion preferences respected.
  • Mobile-first CSS using custom properties, Flexbox and Grid. The narrow layout is the default; wider screens add to it rather than undoing it.
  • A measured colour system. Every text and interface colour was checked against its background for contrast before use, not chosen by eye. One combination that looked obviously correct failed the standard and was replaced.
  • Generated artefacts, never hand-edited. The downloadable CV is produced from a single HTML source, so the document and the site can't drift apart.
  • Continuous deployment. Committing to the main branch builds and publishes automatically, typically inside a minute.
  • Release discipline. Every deployment is version-tagged with a defined rollback path, and changes are reviewed in a local environment before they reach production.

How it's secured

Built against the OWASP Top 10 and verified against a written standard before each release. The controls below are all independently observable from outside — that's deliberate: a security posture you can verify is worth more than one you're asked to take on faith.

  • Minimal attack surface by design. No backend, no database, no authentication and no user accounts — so there is no query to inject into and no session to steal.
  • A strict Content Security Policy with no inline-script exemption. Most sites can't manage this; it's possible here because the site carries no inline scripts, no inline styles and loads nothing from a third-party origin. It is the safety net beneath the output-encoding practices, not a substitute for them.
  • Transport hardening — modern TLS only, with legacy protocol versions refused, strict transport security enforced, and plain HTTP permanently redirected.
  • Defensive response headers covering framing, MIME-type sniffing, referrer leakage, cross-origin isolation, and browser feature permissions that this site has no reason to request.
  • Cross-site scripting handled at the source. Dynamic text is written to the page as text, never parsed as markup — the habit that prevents the class of bug, with the policy above as the second line.
  • Form abuse controls. Submissions are handled off-platform with spam filtering in front of them, so no credentials or delivery infrastructure live in the site itself.
  • Supply chain. Zero production dependencies. Static analysis and secret scanning run on the repository; the full commit history was reviewed for secrets before it was ever made public.
  • Verified, not assumed. Each release is checked against the live response rather than the intended configuration. The first run of that check caught two real defects that visual review had missed.

Measured results

100 / 100Lighthouse performance & accessibility
100 / 100Best practices & SEO
0production dependencies
< 60scommit to live

What I'd do next

Add further projects as they ship, and privacy-respecting analytics — enough to know whether the site is doing its job, without tracking the people reading it. Everything deferred from the first release is written down with the reason, so the backlog is a decision record rather than a wish list.

In daily use 2026

Personal Knowledge Vault

A second brain an AI agent runs with me. Plain markdown files, a written operating specification the agent is held to, and an audit trail of every change since day one — used every day to keep bills, appointments, renewals, upkeep and study on the rails.

Role
Sole designer, operator and maintainer
Stack
Markdown · YAML frontmatter · Obsidian · Dataview · Claude Code · Git
Scale
533 notes, 138 folders, in continuous use since June 2026

The brief

Personal administration isn't a memory problem, it's a scheduling problem wearing a memory problem's clothes. A certification lapses, a subscription renews at a price you'd have argued with, a service interval passes, a warranty expires three weeks before the thing breaks. None of that is forgotten because it was never written down — it's forgotten because nothing brought it back at the moment it mattered.

Every note app I'd tried failed at the same point: filing is manual work, and manual work is the thing that stops. So the requirement wasn't a better place to put notes. It was a system where something else does the filing — where I hand over a statement, a photo of a receipt, a forwarded confirmation, and it lands in the right place with the dates extracted, without me choosing a folder.

Why not an app

I costed the alternatives before building anything, the same way I do on every project. Hosted tools win on polish and lose on the two things that decided it here.

  • An agent needs to read and write the store directly. Behind a proprietary database, every operation becomes an API integration, rate limits and all. Against a folder of text files it's just reading and writing files — which is what these tools are already good at. That single property is what makes the whole system possible.
  • Plain files outlive their software. Markdown and YAML are readable in any text editor, on any machine, in twenty years. The editor, the plugins and the agent are all replaceable around the files; a hosted workspace makes the files hostage to the vendor's roadmap and pricing.
  • No lock-in worth paying for. Databases, boards and automations are what you buy from a hosted tool. None of them were the bottleneck. The bottleneck was filing, and filing is exactly what none of them do.
  • Deliberately accepted: no mobile agent, no notifications, no multi-user permissions, and dashboards that are only as good as the metadata behind them. Recorded as trade-offs with reasoning, not discovered later as disappointments.

The specification is the product

The folders are the least interesting part — any filing scheme works if it's followed. What makes this reliable is a single specification file the agent reads at the start of every session: roughly 5,800 words defining how the system behaves.

  • A routing table mapping each kind of incoming thing to exactly one destination and one template, so filing is a lookup rather than a judgement call made differently each time.
  • A closed tag vocabulary. The agent may propose a new tag; it may not invent one. Uncontrolled tags are how a tag system becomes a synonym problem.
  • A quality bar per note type. A bill without an amount, a due date and its autopay status isn't finished — those are precisely the fields that matter when something is about to charge you or lapse. A goal with no date is a wish.
  • An explicit ask-first list. Deleting, moving, merging suspected duplicates, inventing structure. Anything irreversible or anything that changes the system itself stops and asks.
  • Fabrication rules. Never guess a figure, a provider or a date — write TBD. Never write account numbers, card numbers or credentials into a note; record where the real thing lives.

This is the same discipline as writing a runbook, applied to a non-deterministic operator. You cannot test an agent into behaving; you constrain it, then verify the output.

Four mechanisms that do the work

  • The operating specification — described above. Read every session, revised only with my approval.
  • An append-only change log. One line per change, newest first, from day one. It answers "when did I set that up, and why?" two years later, which is the one thing that cannot be reconstructed after the fact. 229 entries so far.
  • A corrections-to-rules loop. Every time the agent gets something wrong, the correction is logged as a lesson. Lessons are reviewed in batches and become proposed changes to the specification, which I accept or reject. 26 logged, 9 promoted into standing rules. Crucially, logging is not changing — the structure can't drift underneath me between sessions.
  • A renewals radar. One page querying the frontmatter of every note in the vault for anything with an expiry: licences, certifications, warranties, insurance, registrations, domains. This is the component that justifies the whole system, and it needed no code — only consistent metadata.

Two smaller design decisions carry more weight than they look like they should. Attachments live beside the note that references them, never in a single global pile, so a receipt and its record never separate. And because the agent only runs on the desktop, each day's journal carries a handoff section — things captured on a phone, picked up and actioned at the next desktop session. The gap in coverage is designed for rather than wished away.

Where it went wrong, and what that changed

An agent with file access is a capable, confident, and periodically wrong operator. The interesting engineering here is the countermeasures, and every one of them exists because of a real failure rather than a hypothetical.

  • It merged three entities into one record. A multi-vehicle data export was summarised without grouping on the column identifying which vehicle each row belonged to, and one vehicle was credited with all three cars' mileage. The numbers then propagated into another note and would have fed a purchasing decision. Rule added: profile any tabular import before summarising it — row count, and the distinct values of every identity column. If more than one entity is present, split first. If there's no identity column at all, say so rather than letting the folder imply the scope.
  • It published a causal claim off a correlation. A near-perfect cross-tabulation in a third-party dataset looked like a mechanism and got written up as one. It was wrong, and a single additional query would have broken it. Rule added: a suspiciously clean result is a prompt to hunt for the confounder, not a finding — and before asserting a mechanism, run the query that should falsify it.
  • It cited a regulation that doesn't exist. A limit quoted in an industry article was repeated as statute, and nearly went into a formal letter. Rule added: for any legal or regulatory claim that drives a decision, fetch the primary source. Secondary sources are leads, not authority.

The pattern is the point. Each failure became a written rule, in a file the agent reads before it acts again — so what gets fixed is the class of mistake, not the instance. Two of those three were caught by me reading the output, which is the honest limit of the design: this is verification-dependent, and it says so out loud.

Guarding the data itself

This vault holds medical appointments, financial obligations and family records. Convenience is worth nothing if the store underneath it isn't trustworthy, so a few constraints are fixed rather than left to good habits. The behavioural rules above — no credentials in notes, TBD instead of a guess, no structural change without approval — exist for this reason. Three more sit around them:

  • Nothing personal ships. The published starter kit is deliberately empty. It carries the structure, the specification and the templates — no real content, and no sample data lifted from an actual life and lightly disguised.
  • It deliberately does not live in consumer file sync. iCloud Drive, Dropbox and OneDrive reconcile file by file with no awareness of an application holding a file open. Point them at a vault that two programs are writing to and you eventually get conflicted copies, or a truncated note that no longer says what it said yesterday. A plain local folder, synced by something built for the job, avoids the entire class of problem.
  • The audit trail is a safety property, not just a convenience. An autonomous agent with write access to your records should be verifiable rather than merely trusted. Every change is one line in a log I can read, which is what makes it possible to hand over the filing without handing over the oversight.

Measured results

533notes across 138 folders
229changes logged since day one
26 → 9corrections logged, promoted to rules
23note templates in service

The number I care about isn't in that grid: nothing dated has lapsed unnoticed since the radar went in. Every renewal, service interval and expiry now surfaces on one page instead of living in my head.

Build your own

The structure here is generic on purpose, so I've published the skeleton as a template repository: the folder layout, a specification file with your details left as placeholders, ten note templates, the log and corrections files, the renewals queries, and full setup instructions for both the editor and the agent.

The part worth copying is the bootstrap prompt. It doesn't build anything on first run — it interviews you, shows you the structure and the exact specification changes it intends to make, and waits for your approval. A generic vault is one you stop opening in three weeks; the interview is what makes it yours. Expect about 45 minutes, most of it talking.

What I'd do next

Two gaps are worth closing. Dashboards silently under-report when a field is missing, so an empty table looks like good news when it may just be bad metadata — that wants a staleness and completeness check rather than more discipline. And the radar is passive: it's correct whenever I look at it, which is not the same as telling me. A scheduled agent run that surfaces the next thirty days without being asked is the obvious next step.

More to come — these are the first of a series. Get in touch if you'd like to talk about any of it.