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.