Repository navigation
Releases: kbrdn1/kbrdn.dev
Release list
v2.0.0
[2.0.0] - 2026-10-29
A redesign, and new addresses. Every page is rebuilt as rounded panels holding
cards, with a third theme, glass, a mascot, a new K logo, a navbar whose notch
opens into menus, pixel scenes in the heroes and the sections, and four new
pages: the journey (/parcours), the apps, the activity and the contact form.
Articles can be listened to, take the site's design and open their pictures in
the site's viewer; every post and every journey entry gets its own banner. The
major version is for the addresses: up to 1.1.0 both languages shared the same
paths, English by default; French now lives at the root and English under
/en/, so the pages indexed in English answer in French. Every page gets
structured data, the site a sitemap, and an RGAA 4.1.2 pass with a button that
pauses every animation. In production, the gwm article's demo GIF stops
answering 500: it no longer goes through the image server. OG image URLs are
now signed, so production needs NUXT_APP_SECRET.
Added
-
Four pixel shapes for the dither field
(#72). Squares, circles,
triangles or diamonds, after codrops' Bayer dithering demo: one shape is
picked at random on each load and shared by every field of the page
(background, footer, blog banners). Shapes other than squares need bigger
cells; the noise and the pointer trail are sampled accordingly, so they keep
their size on screen. Each shape covers about half a cell and the alpha
ceiling stays at 18%, so text contrast is unchanged. Drawn in two canvas
calls per frame whatever the cell count.localStorage['dither-shape']pins
a shape. -
Pixels that spread on hover
(#72). After hexagoncircle's
<pixel-canvas>: hovering or focusing a card (pinned projects, blog posts,
the blog sidebar card) or an accordion header (experiences, studies) grows
cells of the page's dither shape out from where the pointer came in, which
shimmer, then shrink away on leave. Under the content, never in the way of a
click; nothing on touch, a still frame underprefers-reduced-motion. -
Text links retype on hover
(#73). After effect 1 of
codrops' Line Text Hover Animations: a block cursor runs along the label as
each character retypes through random glyphs, on hover or keyboard focus of
the header and blog navigation (desktop), the footer lists and the projects'
"view all". -
A mascot
(#74). A monkey in a
backwards cap, in 31 moods, shows up where something did not go as planned:
shocked on the 404 page, thinking when no blog post matches, sad when GitHub
does not answer, clapping or nervous after sending the contact form, and
quietly next to the copyright. Small and decorative, always next to text that
already says what happened; 384px webp, 616 KB for the whole set. -
An animated 404 page
(#74). The "404" pixels
arrive scattered and spring back into place, now and then one of them slips
out of line, "Reset" replays the same return, the mascot floats and gets
dizzy while you drag a pixel, the interactive dither field replaces the
diagonal stripes and the subtitle retypes like the titles. Still under
prefers-reduced-motion. -
A journey page,
/parcours(/en/journeyin English)
(#78). Laid out like an
editorial spread (after isrod.eu): an intro, a strip of every role and
degree, then one spread each, the story on the left and the stack and
missions on the right, with "show more". Then the open source work, every
item linked to its pull request or issue: the projects I maintain (gwm,
vellum, LazyCurl, herdr-plugin-gwm, claude-crosstalk, kbrdn-docs), the
contributions (winget, aqua-registry and awesome-ratatui merged, nixpkgs in
review) and the bugs reported upstream. On the home page, the Parcours
accordions give way to a one-line-per-role teaser linking to it. -
An apps page,
/apps
(#79). gwm (available),
vellum (in development) and LazyCurl (archived), each with its features and
licence, links to its site, repository and releases, and for gwm the install
commands of its README. The latest release is read from GitHub instead of
written down. -
A third theme, glass
(#86). The theme button now
cycles light, dark and glass. Glass is the dark theme with translucent panels
blurred over the dither field, which then fills the whole screen, unfaded;
everything styled for dark applies to it. Under
prefers-reduced-transparencythe panels turn opaque again. -
Pictures seen through a pixel layer
(#86). Banners, app
screenshots and the carousel's voxel scenes are redrawn as a grid of the
page's pixel shape, the one of the dither field, each cell the picture's
colour there. Hovering clears the layer block by block from the pointer down
to the crisp picture, and a new picture sweeps in block by block; at once
underprefers-reduced-motion. Banners go through IPX at a few hundred pixels
instead of the 11 MB originals. -
The navbar's notch opens into menus
(#86). Hovering a page in the navbar opens the notch under it into that page's menu: a line of
context, its sections (the latest posts for the blog) and an illustration that
follows the link under the pointer and stays on the last one; it widens as it
opens, and the navbar's
pill moves to the page whose menu is open. The announcement opens gwm's, which
now leads to gwm.kbrdn.dev. Desktop only. -
A face on the site
(#86). A portrait in
/contact's details, through the pixel layer; a small round photo beside the
hero's handle, the drawn avatar on hover; and the hero's mascot now takes turns
with drawn portraits of me. Re-encoded without metadata. -
A pixel on the pointer
(#86). A pixel in the accent
sits on the pointer's tip; now and then it grows block by block into an arcade
invader, hops off in one of a D-pad's eight directions, marches on a block per
frame like in the arcade, sometimes turns into another one, then hops back
onto the tip. A click throws eight blocks along the D-pad. A triple click calls
Ichigo Kurosaki, from his Jump Ultimate Stars sprites (21 moves, Shikai and
Bankai): he grows out of the pixel, plays a move drawn at random (never the
same twice in a row; three are specials whose Getsuga Tenshō flies across the
screen), then folds back; every third click calls him again. The
sprites load on the first triple click (123 KB). A shift click turns the
pixel into a sight that tightens on each click, the system pointer a cross.
Mouse and trackpad only, never underprefers-reduced-motion. -
Illustrations full size
(#86). A click on an
illustration of/parcoursopens it crisp and full size in a native dialog
(Escape, a click beside it or its button close it), as an article's pictures
already zoom. -
Where the companies, clients and schools live
(#86). Under each entry of
/parcours, links to flippad.com and jewely.fr, the clients' sites
(bijouterie-julian.com, crown-dp.com, martin-media.fr), the school's, and the
projects' own site and article. -
Share an article from the device
(#86). Where the browser has
one, a button opens the device's share sheet (Web Share), beside Copy link. -
Featured projects, personal and professional
(#86). Two tabs on the home:
the repositories pinned on GitHub, and the projects built at Jewely x Flippad
in the same cards, the project's logo, its site, its client and its years, a
bar showing when it ran within those years; each leads to it on
/parcours. -
An invader on the footer's edge
(#86). Now and then one of
the pointer's invaders runs in on the footer's top edge, in its own colour of
the theme, and runs back and forth, a trail of pixels behind it that follows
its jumps. Its favourite food pops up here and there, animated (pellets for
the squid, cherries for the crab, stars, mushrooms or cookies for the
others), and it leaps to eat it; it vaults the obstacles that rise in its way
(a rock, spikes, a cactus, a crate) and ducks under the birds that fly at it.
After a while it runs off and another one comes later; a click blows it up
and the cycle goes on. One at a time, only while the footer is in view, never
underprefers-reduced-motion. -
A pixel scene in the pages' hero
(#86). Beside the hero, under
it on a phone, a detailed loop in the theme's palette with no background, the
sun by day, the moon and stars by night: a crab climbs the mountain's trail to
its flag and cheers in confetti on/parcours; an invader sits on a monitor
typing gwm's commands on/apps; another fills a contribution graph on
/activity; on/blog, a writing nook at night after Kirokaze's and
Waneella's rooms, rain over the city, the article typed then sent off as a
paper plane, a ginger cat asleep on the books; a bird posts a letter on
/contact. It plays only on screen and holds still under
prefers-reduced-motion. The pages' OG images show the same scene. -
What is going on right now, on
/activity
([#86](https://github.com/kbrdn1/kbrdn.dev/i...
v1.1.0
[1.1.0] - 2026-10-16
A visual release. Every page gets an animated pixel-dither background that
follows the pointer, the home page is reworked around a stack list, the pinned
GitHub repositories and the real contribution graph, the music player floats
over every page, and page titles decode on load. Two production bugs hidden
behind fallbacks are fixed on the way: GitHub activity was random data, and the
contact form most likely never emailed anything, both because their secrets
were read from a runtime config frozen empty at build time. Dependabot starts
with this release, since it only reads its config from main.
Added
- An animated pixel-dither background, interactive with the pointer
(#51). Two scrolling noise
layers, thresholded through a 4×4 Bayer matrix into a few alpha levels of the
primary orange, drawn at cell resolution and upscaled with
image-rendering: pixelated. On every page, a fixed band over the top 85% of
the viewport fades out downwards, and the pointer leaves a trail that lights
up and pushes the noise. The trail is squishy: it follows the pointer on a
spring, so it lags, overshoots and wobbles before settling, and its brush
stretches along the motion. The pattern is random on each load. The footer and
the blog banners (article header and list thumbnails, on top of their image)
carry the same field, animated but not interactive. Canvas 2D, no dependency:
the noise is generated in a Web Worker, the colour follows the theme toggle,
the animation pauses off screen and in hidden tabs, a single frozen frame is
drawn underprefers-reduced-motion, and touch input leaves no trail. The
densest level stays at 18% alpha: in dark, text that met the 4.5:1 contrast
still does over it; in light, small grey labels drop to about 4.0:1 under
the densest cells, a trade-off kept for a visible field. - A floating "Listening to" player
(#54). The music player
leaves the home page's About section for a button in the bottom-right corner
of every page, which opens it floating at the bottom, centred and as wide as the content
(Escape closes it). Mounted outside
the layouts, so the music keeps playing from the home page to the blog; the
playlist is only fetched on first open. - Decoding page titles
(#53). On load, the main title of
every page (home, blog, articles) resolves left to right out of random glyphs,
in up to 2.4 s, without shifting the layout; the server sends the plain
text, screen readers get the real name, andprefers-reduced-motionkeeps
it still. - Weekly dependency updates with Dependabot
(#56). Bun packages, GitHub
Actions and theoven/bunbase image, as PRs againstdev, with minor and
patch bumps grouped per ecosystem. The PR checks used to fail such PRs by
construction (branch name, no linked issue); they now let PRs opened by
Dependabot through, the title check still applies. Dependabot reads its
config frommain, so it starts once this version is released.
Changed
-
The home page, reworked
(#53). The skills tabs
give way to a stack list (a column of categories, then each one's tools with
their icon, linking to its official site). The contact links (GitHub,
Twitter, LinkedIn) move into the hero as icon-only ghost buttons, next to
"Book a call", in place of the "View on GitHub" and "Read the blog" buttons; the Contact section goes.
The "Open to opportunities" badge turns green, the avatar loses its stripes,
border and hover zoom, and the About text loses its em dashes. Featured projects are
now the repositories pinned on GitHub, as compact cards (logo or language
icon, stars, forks, a language bar along the bottom edge). GitHub activity
opens on a "Recent" tab, the rolling last 12 months as on the GitHub
profile, and its month labels no longer overlap. Loading skeletons now have
the shape of what they stand for, one per section. -
A cleaner page frame
(#51). Outside the 80rem
column, a muted 24px grid and a border along the column, continuing the
header's, fade out downwards; the column itself carries no grid. Drawn once
by the layout instead of side zones repeated in every page (diagonal stripes
on the home page, a full-height grid on the blog). The full-height borders
around the column, the separators between sections and the corner triangles
are gone; corners stay sharp. The dark background goes from
#1a1a1ato#121212, and the footer gets an opaque background, with the
blur over its content column.
Fixed
- GitHub activity showed made-up numbers in production
(#53). The token was read
from a runtime config frozen at build time, empty in the Docker image, so the
API fell back to random data on every request. The server now reads
GITHUB_TOKENat request time, and returns an error instead of random data
when it cannot reach GitHub. - Contact form messages were most likely never emailed
(#53). Same cause with the
Resend key: without it, the form only logged the message and reported
success. The key is now read at request time too.
v1.0.2
[1.0.2] - 2026-09-19
A checks-only release: the site served in production is the same as 1.0.1.
What changes is what stands between a change and production — the OG images,
which broke twice during 1.0.1 without any check noticing, are now compared
byte for byte in CI — and the release guide, which 1.0.1 proved wrong in four
places.
Added
- CI now checks the OG images
(#44). They are rendered at
request time, so lint, typecheck and build never ran them, and the oxfmt
reformat of 1.0.1 broke them twice with CI green. The build job now serves
.outputand compares the OG images of/,/blogand/blog/gwmbyte for
byte against references committed underscripts/og-smoke/— rendering is
identical across platforms, a reference made on macOS matches production bit
for bit. A gradient split across lines fails it with a 500, single quotes in
main.cssfail it on the image itself. An intended change to an OG image
goes throughscripts/og-smoke.sh --update.
Fixed
docs/RELEASE.mddescribed the release it was supposed to guide wrongly
(#43). Cutting 1.0.1
contradicted four passages: theLinked issuecheck applies to pull requests
intodevtoo, not onlymain;mainalso requires an approval, so the
promotion is merged with--admin— which bypasses the required checks as
well, and must only come after they are green;/api/healthcannot tell the
tag's image from the onedeploy.ymlships on the merge, so the proof of a
release isgh release view; and re-running an old tag does not rebuild the
image, it rewrites the release notes from the tag's tree.
v1.0.1
[1.0.1] - 2026-09-19
A maintenance release. The site itself changes in one place — the portfolio no
longer claims a job that ended — but most of this version is about the checks
around it: the typecheck had silently stopped running in CI, and linting and
formatting move to the oxc toolchain.
Changed
-
The site no longer says I work at Jewely x Flippad
(#33). The contract ended in
September 2026, but the hero badge, the footer status and the OG image still
read "In contract at Jewely x Flippad", and the experience was still flagged
current. The experience now closes on2026-09with the role renamed to Full
Stack Engineer, and the status reads "Open to opportunities". -
Linting and formatting move to oxc
(#37).oxlintreplaces
ESLint and@nuxt/eslint, with the rules migrated from the old config. Not
carried over: the template-only rules of eslint-plugin-vue,no-unused-vars
andprefer-constinside.vuefiles (oxlint does not run them on SFCs), and
three Nuxt-specific rules that would have kept an ESLint plugin around. Unused
locals in.vuefiles are caught by the typecheck instead (noUnusedLocals).
oxfmtformats the code — Markdown and MDX are left alone, and so are the
satori OG components and the quotes in CSS, both of which broke the OG images
in a way no CI step could see — and CI now fails on unformatted code. The one-off reformat commit is listed in
.git-blame-ignore-revs.
Fixed
- The typecheck was not running in CI
(#35).vue-tscwas not a
dependency, sonuxt typecheckfetched it throughnpx, which resolved its
typescriptpeer tolatest— TypeScript7.0.2, which no longer exports
./lib/tsc.vue-tscdied on load, andcontinue-on-error: trueon the step
kept the job green: the v1.0.0 release run (2026-08-14) already failed on it.vue-tscand
typescript@~5.9are now pinned devDependencies, the step fails the job
again, and the 38 type errors it had been hiding are fixed (mostly unchecked
index access;titlesadded to the pages schema, the dead i18nlazy
option removed).
v1.0.0
[1.0.0] - 2026-08-14
First tagged version of the site. The site itself was already live on
kbrdn.dev — what this version adds is everything that was
missing around it: a way to say which version is deployed, a way to go back to
a previous one, and a release that a tag actually performs rather than merely
records.
Versioning starts at 1.0.0 rather than 0.x: the product is in production,
and a 0. would misdescribe it.
Added
-
A release flow, where there was none
(#18). Deployment worked, but
nothing tied it to a version: images were tagged:mainand:dev, both
mutable,package.jsoncarried noversionfield at all, and nobody could
answer "which version is live on pre-prod right now".Tags now drive deployment.
vX.Y.Z-rc.Nbuilds an immutable
ghcr.io/kbrdn1/kbrdn.dev:vX.Y.Z-rc.N, ships it to preprod and opens a
GitHub pre-release;vX.Y.Zdoes the same for prod and publishes the
release. Pushing todevstill redeploys preprod for day-to-day iteration —
that path is untouched, anddeploy.ymlonly ever wakes on branches while
release.ymlonly wakes on tags.The routing lives in
scripts/resolve-release-tag.shrather than inline YAML,
because getting it wrong ships an unvalidated candidate to production;
scripts/resolve-release-tag.test.shcovers it, malformed tags included. -
GET /api/healthreturning{ status, version, sha, env }, all three
baked in at build time. The containerHEALTHCHECKnow points at it instead
of/: the old probe went green as soon as the home page rendered
server-side, which stays true even when the API layer behind/api/github/*
and/api/contactis dead. It uses a real GET, notwget --spider— that
sends a HEAD, and h3 does not route HEAD to a.gethandler. -
Guardrails that hold the protocol up, rather than a document asking
politely: a stable tag must be an ancestor ofmainand a candidate an
ancestor ofdev;package.jsonmust match the tag; the notes file must
exist. All four run before anything is built or deployed. The GitHub
release is only published once/api/healthhas confirmed, from the outside,
that the expected version and environment are serving traffic. -
scripts/github-deploy.shis versioned and hardened. The piece that
reconciles the workflow — which configures over environment variables — with
deploy.sh, which parses flags, turned out to be aForceCommandwrapper
living only on the VPS: the centre of the deployment pipeline was invisible
from the repository. It now only exports an allowlist (ENV,IMAGE,
GITHUB_TOKEN,RESEND_API_KEY,NUXT_STUDIO_TOKEN) and refuses anything
else,PATHincluded —deploy.shresolvesdocker,nginxand
systemctlthrough it, as root. -
A promo banner at the top of the README
(#24), dark and light, built
around the site's own monogram.
Changed
-
Rollback actually rolls back
(#18).scripts/deploy.sh
used todocker rm -fthe outgoing colour at the end of every deploy, while
--rollbackrequired exactly that container — at steady state a single
colour runs, so the option could only ever fail, and silently so until the
day it was needed. The outgoing colour is now stopped and kept, and the
immutable per-version images make
--image ghcr.io/kbrdn1/kbrdn.dev:vX.Y.Za real return to a known version
rather than a colour flip. -
Deployments to one environment are serialised. The protocol itself used
to put two in flight: merging intomainwakesdeploy.yml, pushing the tag
right after wakesrelease.yml, and their workflow-level concurrency groups
differ. Twodeploy.sh --env prodwould then target the same container names
and the same nginx upstream. Both jobs now share avps-deploy-<env>key. -
Containers carry a
kbrdn.versionlabel, sodocker inspectanswers the same
question as/api/healthfrom inside the VPS. -
Re-running a released tag no longer rebuilds its image: registry tags are
mutable and the build is not reproducible, so republishing could quietly make
:vX.Y.Zstop meaning what it meant — the very property version rollback
rests on.
Docs
docs/RELEASE.md— the protocol, the repo's knobs, the
real deployment chain (ForceCommand→github-deploy.sh→deploy.sh), and
the VPS synchronisation procedure.- Two traps found the hard way and written down: overwriting
/srv/deploy.sh
while it runs breaks the deploy in progress (bash reads a script by offset),
and every PR intomainmust close an issue — the release PR included, so
each version needs its own.
v1.0.0-rc.2
[1.0.0-rc.2] - 2026-08-14
First tagged version of the site. The site itself was already live on
kbrdn.dev — what this version adds is everything that was
missing around it: a way to say which version is deployed, a way to go back to
a previous one, and a release that a tag actually performs rather than merely
records.
Versioning starts at 1.0.0 rather than 0.x: the product is in production,
and a 0. would misdescribe it.
Added
-
A release flow, where there was none
(#18). Deployment worked, but
nothing tied it to a version: images were tagged:mainand:dev, both
mutable,package.jsoncarried noversionfield at all, and nobody could
answer "which version is live on pre-prod right now".Tags now drive deployment.
vX.Y.Z-rc.Nbuilds an immutable
ghcr.io/kbrdn1/kbrdn.dev:vX.Y.Z-rc.N, ships it to preprod and opens a
GitHub pre-release;vX.Y.Zdoes the same for prod and publishes the
release. Pushing todevstill redeploys preprod for day-to-day iteration —
that path is untouched, anddeploy.ymlonly ever wakes on branches while
release.ymlonly wakes on tags.The routing lives in
scripts/resolve-release-tag.shrather than inline YAML,
because getting it wrong ships an unvalidated candidate to production;
scripts/resolve-release-tag.test.shcovers it, malformed tags included. -
GET /api/healthreturning{ status, version, sha, env }, all three
baked in at build time. The containerHEALTHCHECKnow points at it instead
of/: the old probe went green as soon as the home page rendered
server-side, which stays true even when the API layer behind/api/github/*
and/api/contactis dead. It uses a real GET, notwget --spider— that
sends a HEAD, and h3 does not route HEAD to a.gethandler. -
Guardrails that hold the protocol up, rather than a document asking
politely: a stable tag must be an ancestor ofmainand a candidate an
ancestor ofdev;package.jsonmust match the tag; the notes file must
exist. All four run before anything is built or deployed. The GitHub
release is only published once/api/healthhas confirmed, from the outside,
that the expected version and environment are serving traffic. -
scripts/github-deploy.shis versioned and hardened. The piece that
reconciles the workflow — which configures over environment variables — with
deploy.sh, which parses flags, turned out to be aForceCommandwrapper
living only on the VPS: the centre of the deployment pipeline was invisible
from the repository. It now only exports an allowlist (ENV,IMAGE,
GITHUB_TOKEN,RESEND_API_KEY,NUXT_STUDIO_TOKEN) and refuses anything
else,PATHincluded —deploy.shresolvesdocker,nginxand
systemctlthrough it, as root. -
A promo banner at the top of the README
(#24), dark and light, built
around the site's own monogram.
Changed
-
Rollback actually rolls back
(#18).scripts/deploy.sh
used todocker rm -fthe outgoing colour at the end of every deploy, while
--rollbackrequired exactly that container — at steady state a single
colour runs, so the option could only ever fail, and silently so until the
day it was needed. The outgoing colour is now stopped and kept, and the
immutable per-version images make
--image ghcr.io/kbrdn1/kbrdn.dev:vX.Y.Za real return to a known version
rather than a colour flip. -
Deployments to one environment are serialised. The protocol itself used
to put two in flight: merging intomainwakesdeploy.yml, pushing the tag
right after wakesrelease.yml, and their workflow-level concurrency groups
differ. Twodeploy.sh --env prodwould then target the same container names
and the same nginx upstream. Both jobs now share avps-deploy-<env>key. -
Containers carry a
kbrdn.versionlabel, sodocker inspectanswers the same
question as/api/healthfrom inside the VPS. -
Re-running a released tag no longer rebuilds its image: registry tags are
mutable and the build is not reproducible, so republishing could quietly make
:vX.Y.Zstop meaning what it meant — the very property version rollback
rests on.
Docs
docs/RELEASE.md— the protocol, the repo's knobs, the
real deployment chain (ForceCommand→github-deploy.sh→deploy.sh), and
the VPS synchronisation procedure.- Two traps found the hard way and written down: overwriting
/srv/deploy.sh
while it runs breaks the deploy in progress (bash reads a script by offset),
and every PR intomainmust close an issue — the release PR included, so
each version needs its own.