Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
9850fce
Add the v2 theme engine, design system and Astro baseline
michaeljolley Jul 31, 2026
f6bfac1
Add topic first URLs, the taxonomy map and an exhaustive redirect map
michaeljolley Jul 31, 2026
02d544d
Build every static page, retire v1 and turn on the CI gates
michaeljolley Jul 31, 2026
adae8f9
Phase 4: Supabase schema, GitHub auth, profiles and accounts
michaeljolley Jul 31, 2026
2545260
Likes: anonymous toggling, real counts everywhere
michaeljolley Jul 31, 2026
1f7e919
Comments, comment likes, and a report path that records something
michaeljolley Jul 31, 2026
5fe14d9
Dev disaster submissions, with a title drafted on the way in
michaeljolley Jul 31, 2026
7b1e09d
Badges that can actually be earned, and a shelf that says how far off…
michaeljolley Jul 31, 2026
f6598ed
A shelf that reads right when it is empty, and the spec for filling it
michaeljolley Jul 31, 2026
cb415a0
Move v2 off the live database, and stop the tooling pointing back at it
michaeljolley Jul 31, 2026
9d0af5a
Stop a failed load claiming there are zero replies
michaeljolley Jul 31, 2026
939847a
Privacy and terms, written from the code rather than from a template
michaeljolley Jul 31, 2026
4324ee6
A live redirect check, and a rule that has been broken in production …
michaeljolley Jul 31, 2026
3b3f03e
Notification emails, and the act of featuring that one of them was mi…
michaeljolley Jul 31, 2026
e93ac26
Empty is the shipping configuration
michaeljolley Jul 31, 2026
fe456ba
A checklist for a project that does not exist yet
michaeljolley Jul 31, 2026
39a0a36
A moderated comment stops naming the person
michaeljolley Jul 31, 2026
a4809d2
Make "is netlify.toml being read" a question with an answer
michaeljolley Jul 31, 2026
7fff47f
v1 sends no email, and the copy now says so
michaeljolley Jul 31, 2026
c92e94c
The featured badge, and a place to see your own stories
michaeljolley Jul 31, 2026
8c53c09
Stop advertising pages that ask not to be indexed
michaeljolley Aug 1, 2026
076dbf1
Ignore every env file, not the two that existed once
michaeljolley Aug 1, 2026
6307c39
Name the video things instead of counting them
michaeljolley Aug 1, 2026
b953620
Give the lead card its summary back, and measure the space
michaeljolley Aug 1, 2026
af8d026
Make CI say why it cannot see the content, instead of dying in git
michaeljolley Aug 1, 2026
3f13722
A true README, and a privacy page that admits the Drip box exists
michaeljolley Aug 1, 2026
22ab2c3
Take the wall off the seed file and put it on the database
michaeljolley Aug 1, 2026
2e9a97f
Measure both alternatives to the lead card's align-content
michaeljolley Aug 1, 2026
52a121e
Record what the aspect-ratio change costs and what it buys
michaeljolley Aug 1, 2026
889c842
Make the lead slot require prose, and fail the build without it
michaeljolley Aug 1, 2026
7848c62
Hold unpublished picks out of the Start here rail
michaeljolley Aug 1, 2026
47cb715
Ask CI for a deploy key instead of a token, and name the blind spot
michaeljolley Aug 1, 2026
cbff816
Make a story anonymous or authored, never neither
michaeljolley Aug 1, 2026
fb1ffa7
Going private hides the name everywhere, not just on the profile
michaeljolley Aug 1, 2026
5ee3e8b
Show a person their own pending stories on their own profile
michaeljolley Aug 1, 2026
5acc64a
Stop the Start here rail leaving a coloured hole on the front page
michaeljolley Aug 1, 2026
5712463
Measure whether every card covers its own grid cell
michaeljolley Aug 1, 2026
c9f5c80
Make published the default and asking for drafts something you say ou…
michaeljolley Aug 1, 2026
778074c
Run the dev disaster row logic instead of reading it
michaeljolley Aug 1, 2026
c7e478b
Refuse a scheme for being a scheme rather than for failing to parse
michaeljolley Aug 1, 2026
4f7a4ab
Run the comment rules instead of reading them
michaeljolley Aug 1, 2026
65d2cfc
Find the pages with grids on them rather than remembering them
michaeljolley Aug 1, 2026
2f4d8ca
Lead with the newest thing made, not the newest thing survived
michaeljolley Aug 1, 2026
3ef0ae9
Give the most read page type the rail every other page already had
michaeljolley Aug 1, 2026
feb4ce0
Wrap the topics so a phone can reach all of them
michaeljolley Aug 1, 2026
304a2aa
Add a share menu, and record intents rather than shares
michaeljolley Aug 1, 2026
6dc27d9
Count the intent once, and stop naming a slug Michael owns
michaeljolley Aug 1, 2026
cabfbe3
Audit a disclosure without hiding the page behind it
michaeljolley Aug 1, 2026
d557fb9
Check that every id a page points at exists on that page
michaeljolley Aug 1, 2026
fbd7b57
Michael's edits: page head spacing, stacked rail actions, bare like c…
michaeljolley Aug 1, 2026
ee25703
Put the platform marks back in the share menu, monochrome
michaeljolley Aug 1, 2026
5d720b9
Make every gate that reads dist name the tree it read
michaeljolley Aug 1, 2026
18888b3
Split the one contact address into two role addresses
michaeljolley Aug 1, 2026
6ad9a98
Give each prose heading level its own colour, resolved per theme
michaeljolley Aug 1, 2026
fcece9e
Stop a markdown body putting a second h1 on the page
michaeljolley Aug 1, 2026
b05c507
Stop the build provenance calling a clean checkout dirty
michaeljolley Aug 1, 2026
9dc6245
Write down what is known about the one axe failure and close it
michaeljolley Aug 1, 2026
786046d
Stop ringing the radio dot and the consent box on a mouse click
michaeljolley Aug 1, 2026
4d0283c
Cleaning up styles
michaeljolley Aug 2, 2026
48ede85
Updating CI workflow
michaeljolley Aug 3, 2026
94676eb
Updating ci workflow
michaeljolley Aug 3, 2026
832c942
Stating base table and function grants in the migration chain
michaeljolley Aug 3, 2026
072e70a
Merge pull request #21 from baldbeardedbuilder/dev/mjolley/fix-base-t…
michaeljolley Aug 3, 2026
7bb6eb4
Merge origin/main into design-account-page
michaeljolley Aug 3, 2026
482b2c6
Design the account settings experience
michaeljolley Aug 3, 2026
e77bd64
Merge pull request #22 from baldbeardedbuilder/dev/mjolley/design-acc…
michaeljolley Aug 3, 2026
7bccb9d
Style the empty profile activity state
michaeljolley Aug 3, 2026
673c352
Merge pull request #23 from baldbeardedbuilder/dev/mjolley/design-acc…
michaeljolley Aug 3, 2026
92ef317
Vary account link label examples
michaeljolley Aug 3, 2026
485a2be
Merge pull request #24 from baldbeardedbuilder/dev/mjolley/design-acc…
michaeljolley Aug 3, 2026
a4789c0
Activate transactional email notifications
michaeljolley Aug 3, 2026
d90394c
Integrate email settings with account redesign
michaeljolley Aug 3, 2026
1af512a
Make taxonomy generation deterministic
michaeljolley Aug 3, 2026
d2aefa3
Merge pull request #25 from baldbeardedbuilder/dev/mjolley/build-emai…
michaeljolley Aug 3, 2026
5780577
Open YouTube cards in new tabs
michaeljolley Aug 3, 2026
25e9eee
Fix article responsive gutters
michaeljolley Aug 3, 2026
f94df76
Merge pull request #26 from baldbeardedbuilder/dev/mjolley/fix-blog-r…
michaeljolley Aug 3, 2026
763e9b2
Display timed video transcripts
michaeljolley Aug 3, 2026
8b54ff2
Organize transcripts by topic
michaeljolley Aug 3, 2026
e2319e8
Keep responsive header topics on one line
michaeljolley Aug 3, 2026
213f780
Merge remote-tracking branch 'origin/dev/mjolley/v2' into dev/mjolley…
michaeljolley Aug 3, 2026
d672936
Simplify transcript paragraphs
michaeljolley Aug 3, 2026
300deb6
Merge remote-tracking branch 'origin/dev/mjolley/v2' into dev/mjolley…
michaeljolley Aug 3, 2026
26628a0
Remove intros from video pages
michaeljolley Aug 4, 2026
248bb8b
Add short as a content type
michaeljolley Aug 4, 2026
9c99812
Fix Supabase callback trailing slash
michaeljolley Aug 4, 2026
e28e3a9
Merge latest v2 for auth callback fix
michaeljolley Aug 4, 2026
cc7d772
Add video inclusion control
michaeljolley Aug 4, 2026
6d38a5a
Merge remote-tracking branch 'origin/dev/mjolley/v2' into dev/mjolley…
michaeljolley Aug 4, 2026
e8e70d0
Add Panda syntax theme
michaeljolley Aug 4, 2026
fd0b45c
Merge Panda syntax theme into v2
michaeljolley Aug 4, 2026
dc242d7
Show engagement counts in content listings
michaeljolley Aug 4, 2026
687e665
Fix CI performance budget
michaeljolley Aug 4, 2026
ba81c04
Sort theme options alphabetically
michaeljolley Aug 4, 2026
ed4db2b
Revise content topic taxonomy
michaeljolley Aug 4, 2026
b200968
Make authentication copy provider agnostic
michaeljolley Aug 4, 2026
5a967b5
Rename Copilot topic to Copilot AI
michaeljolley Aug 4, 2026
415ec53
Add complete content archive
michaeljolley Aug 4, 2026
043d743
Merge latest v2 for homepage archive
michaeljolley Aug 4, 2026
c1b6376
Redesign search experience
michaeljolley Aug 4, 2026
414225f
Merge remote-tracking branch 'origin/dev/mjolley/v2' into dev/mjolley…
michaeljolley Aug 4, 2026
a9b55d0
Refine submission and code display defaults
michaeljolley Aug 4, 2026
7fa07e8
Fix code block height cap
michaeljolley Aug 4, 2026
1567a6b
Merge dev/mjolley/v2 into taxonomy fix
michaeljolley Aug 5, 2026
6e5e357
Fix taxonomy JSON recovery
michaeljolley Aug 5, 2026
4216071
Merge pull request #27 from baldbeardedbuilder/dev/mjolley/fix-taxono…
michaeljolley Aug 5, 2026
2c43f48
Updating content
michaeljolley Aug 5, 2026
748d085
Regenerate taxonomy after content update
michaeljolley Aug 5, 2026
87958f8
Keep tall code blocks keyboard accessible
michaeljolley Aug 5, 2026
d3283d1
Updating taxonomy
michaeljolley Aug 6, 2026
945ef8e
Merge branch 'dev/mjolley/v2' of https://github.com/baldbeardedbuilde…
michaeljolley Aug 6, 2026
9b38321
Updating README
michaeljolley Aug 7, 2026
737e004
fix: read the profile through the service role in middleware (#28)
michaeljolley Aug 7, 2026
958d532
feat: sign in with GitHub, Discord, or Twitch (#29)
michaeljolley Aug 7, 2026
0eec3d4
fix: sign out, list every provider, and write the link route (#30)
michaeljolley Aug 7, 2026
f26b528
Exempt the host from the daily disaster submission limit
michaeljolley Aug 7, 2026
0f88236
Merge dev/mjolley/ideal-winner: exempt the host from the daily disast…
michaeljolley Aug 7, 2026
27782dd
Merge branch 'main' into dev/mjolley/v2
michaeljolley Aug 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions .env-sample

This file was deleted.

54 changes: 54 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copy to .env and fill in. Everything here is also set on Netlify, per environment.
#
# The site builds and runs with none of these. Supabase driven parts stay quiet rather
# than failing, so a fork or a first clone works before anybody has been given keys.
# See supabase/README.md for where each value comes from.

# Safe to expose. Ships in the browser bundle by design.
PUBLIC_SUPABASE_URL=

# The v2 project ref, for pnpm types. Not defaulted on purpose: the old ref still serves
# the live site and regenerating against it would quietly empty the generated types.
SUPABASE_PROJECT_REF=

PUBLIC_SUPABASE_ANON_KEY=

# Never expose. Bypasses row level security completely. Server only, and it is what
# every write on the site goes through.
SUPABASE_SERVICE_ROLE_KEY=

# Rotatable secret used to hash a visitor's IP into a like dedupe token. Rotating it
# forgets who liked what without ever having stored an address. Any long random string.
LIKE_IP_SECRET=

# Drafts a title, a one line summary and a slug for a submitted dev disaster. Without a
# key the drafter falls back to the story's own opening line and never calls out, which
# works but reads flatter. AI_API_URL and AI_MODEL default to OpenAI chat completions and
# gpt-4o-mini, so only the key is usually needed.
AI_API_KEY=
AI_API_URL=
AI_MODEL=

# Used once, after a data load, if the badge matcher is to key on stable Twitch ids
# rather than on logins. Not needed at build time or at runtime. See docs/backfill.md.
TWITCH_CLIENT_ID=
TWITCH_CLIENT_SECRET=

# PARKED FOR V1. The three below are listed for completeness and must stay unset. v1 sends
# no email of any kind, so there is no sender, no address and no drain on a timer. Setting
# any of them is step one of turning notifications on, which is a decision with a real
# ongoing cost attached, not a configuration gap to be filled in. docs/notifications.md is
# the procedure, including the copy on submit, privacy, terms and account that has to
# change back at the same time. docs/new-project.md deliberately omits all three.
#
# RESEND_API_KEY Mail provider key. src/lib/mail.ts is written but nothing calls it.
# MAIL_FROM From address. Would need a domain verified with the provider.
# NOTIFY_SECRET Bearer token for the drain at /api/notifications/. Unset, that route
# 404s every request before it touches the database, which is the only
# reason it is safe to leave in the route table. The table it would drain
# does not exist either: the enqueue trigger and email_outbox are both
# held in supabase/deferred/, so nothing queues and there is nothing to
# send. Do not read these as "configured off". They are absent.
RESEND_API_KEY=
MAIL_FROM=
NOTIFY_SECRET=
8 changes: 8 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Generated artifacts are committed so CI can regenerate them and diff. That only works
# if the checked out bytes match what the generator writes, so these stay LF on every
# platform regardless of core.autocrlf.
src/styles/themes.css text eol=lf
src/lib/themes.generated.ts text eol=lf
src/lib/ec-themes.generated.mjs text eol=lf
src/config/taxonomy.json text eol=lf
public/_redirects text eol=lf
190 changes: 190 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
name: CI

on:
push:
pull_request:
workflow_dispatch:

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: Build and gates
runs-on: ubuntu-latest
timeout-minutes: 30

steps:

- uses: actions/checkout@v7
- name: Check out the content submodule
env:
CONTENT_DEPLOY_KEY: ${{ secrets.CONTENT_DEPLOY_KEY }}
run: |
if [ -z "$CONTENT_DEPLOY_KEY" ]; then
echo "::error::CI cannot read src/content, so no collection would have any entries."
echo "src/content is a git submodule pointing at michaeljolley/content, which is"
echo "private. The default GITHUB_TOKEN is scoped to this repository only and"
echo "cannot clone another one."
echo ""
echo "Fix, once, by hand:"
echo " 1. ssh-keygen -t ed25519 -C 'baldbeardedbuilder.com CI' -f content-ci -N ''"
echo " 2. On michaeljolley/content, Settings, Deploy keys, Add deploy key."
echo " Paste content-ci.pub. Leave write access UNCHECKED, a build only reads."
echo " 3. On this repository, save the private half, the whole content-ci file"
echo " including its BEGIN and END lines, as the secret CONTENT_DEPLOY_KEY."
echo " 4. Delete both local files. GitHub keeps the only copies that matter."
echo ""
echo "A deploy key rather than a token on purpose. A token expires and is tied to"
echo "a person, so it brings this same failure back later without warning."
echo ""
echo "This job fails rather than building without content, because an empty"
echo "collection set makes every gate below pass for the wrong reason."
exit 1
fi
mkdir -p ~/.ssh
chmod 700 ~/.ssh
# printf rather than echo, because a key is worthless if its final newline is lost.
printf '%s\n' "$CONTENT_DEPLOY_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -t rsa,ecdsa,ed25519 github.com >> ~/.ssh/known_hosts 2>/dev/null
git submodule update --init --depth 1 src/content
if [ ! -f src/content/content.config.ts ]; then
echo "::error::src/content was cloned but content.config.ts is not in it."
exit 1
fi
echo "src/content present, $(find src/content -name '*.md' | wc -l) markdown files."

- uses: pnpm/action-setup@v6

- uses: actions/setup-node@v7
with:
node-version: 24
cache: pnpm

- run: pnpm install --frozen-lockfile

# Fails if a generated artifact was hand edited. themes.css, the font CSS, the
# taxonomy map and _redirects are all outputs, never inputs.
- name: Generated artifacts are in sync
run: pnpm gen:check

- name: Unit and redirect tests
run: pnpm test

# The baseline was trimmed to the two legacy tables v2 actually reads, so a
# migration leaning on something that went would only fail on a db push against
# a fresh project, which is the worst place to find out.
- name: Migration chain is self contained
run: pnpm check:migrations

# Catches the class of mistake that a build will happily ship: a Supabase column
# that changed shape under a query, a nullable view column read as if it were not.
- name: Types
run: pnpm check

- name: Build
run: pnpm build

# The step above is the first thing in this job that produces dist, and four tests
# in redirects.build.test.mjs need it. They were only in the run above, before the
# build, so they skipped on every run and reported green while asserting nothing.
# Run again here, where dist exists. REQUIRE_DIST turns the skip into a failure, so
# this cannot quietly stop working again if the steps are ever reordered.
- name: Redirect tests against the real build
run: pnpm test
env:
REQUIRE_DIST: '1'

# Reads the built output rather than the source, because the sitemap and the
# Pagefind index are generated and so are never reviewed by a person. Catches a
# parked route that still ships, and any page listed in the sitemap whose own
# markup says noindex.
- name: Shipped output
run: pnpm check:dist

# In this job rather than the browser one because it needs no browser, and it reads
# source as well as dist. /report/ is prerender = false, so it writes no file and
# every gate that works from the built output is blind to it. a11y is the exception,
# since it starts a dev server for exactly that reason.
- name: Published addresses
run: pnpm check:emails

# Both browser gates need dist, and a build is slow enough that handing it over
# beats building it three times.
- name: Upload dist
uses: actions/upload-artifact@v7
with:
name: dist
path: dist
retention-days: 3

a11y:
name: Accessibility and layout
runs-on: ubuntu-latest
needs: build
timeout-minutes: 30

steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile

- uses: actions/download-artifact@v8
with:
name: dist
path: dist

- run: pnpm exec playwright install --with-deps chromium
- run: pnpm a11y

# Rides along in this job because it is the only one that pays for a browser, and
# installing chromium twice to run a second thirty second check is not worth it.
# Different question from accessibility, same requirement: a laid out page.
- name: Layout geometry
run: pnpm check:layout

# Counts intents rather than measuring anything, so it is its own step: a doubled
# count has no visible symptom, and a failure here should not read as a layout one.
- name: Share intents
run: pnpm check:share

# Separate from the accessibility job on purpose. axe only reports a missing id when
# the element needed a name to be usable, so this catches a class that job is right
# to stay quiet about, and a failure here should not read as an axe one.
- name: Id references
run: pnpm check:aria

# Rides along here because it needs a browser and a built dist. Sixteen themes times
# five heading levels is 96 computed colors, and the failure being guarded is a
# heading that reads as body text or as a link rather than one that looks broken.
- name: Prose heading color
run: pnpm check:headings

perf:
name: Performance budget
runs-on: ubuntu-latest
needs: build
timeout-minutes: 30

steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile

- uses: actions/download-artifact@v8
with:
name: dist
path: dist

- run: pnpm exec playwright install --with-deps chromium
- run: pnpm perf
39 changes: 37 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# build output
dist/
.netlify/
# generated types
.astro/

# Copied out of Fontsource by scripts/gen-fonts.mjs on every build. Fontsource is the
# source of truth, so a committed copy could only ever drift away from it.
public/fonts/

# dependencies
node_modules/

Expand All @@ -14,11 +19,41 @@ pnpm-debug.log*


# environment variables
.env
.env.production
#
# Ignore every .env variant and name the exceptions, rather than naming the variants.
# This rule used to be the two files that happened to exist the day it was written, which
# meant .env.local, .env.audit-bak or anything else a debugging session leaves behind was
# untracked AND unignored, so one git add -A stages real credentials into a public repo.
# Now an unknown .env file is ignored by default and only an explicitly allowed one is
# tracked. tests/secrets.test.mjs asserts nothing env shaped is ever left visible.
.env*
!.env-sample
!.env.example

# The same thing without the leading dot. env.local and env.backup get created by mistake
# often enough to be worth a rule, and tests/secrets.test.mjs proved this case was still
# open after the rule above was widened. Anchored to the root with a leading slash so it
# cannot reach src/env.d.ts, which is tracked source and must stay visible.
/env*

# Screenshot output from the theme and state passes. Large, binary, and superseded on
# every run, so it belongs in the session files rather than in history.
#
# The harnesses that produce them are deliberately NOT ignored. They have to sit in the
# repo root to resolve their imports, and they are meant to be deleted once the pass is
# done. Left visible in git status, a leftover harness nags until it is removed. Ignored,
# it would quietly accumulate.
shots-*/

# macOS-specific files
.DS_Store

# jetbrains setting folder
.idea/

# Supabase CLI scratch, holds the linked project ref
supabase/.temp/

# Resumable transcript cache and Supabase import files. These are large backfill artifacts,
# not application source, and can be regenerated from the video catalogue.
/backfill/
Loading