Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ Lokal laufen lassen:
### Update aller Deps

`bundle update`

### Projektnotizen

Wichtige, nicht offensichtliche Fakten zum Repo (Build/Deploy, CSP, SEO)
liegen unter [`memory/`](memory/README.md). Der Ordner wird nicht auf die
Website veröffentlicht.
6 changes: 6 additions & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ exclude:
- Gemfile
- Gemfile.lock
- bin/
- memory/ # internal project notes, not part of the published site
- README.MD
- package.json
- package-lock.json
- scripts/
- purgecss.config.js

# But make sure to process the following directories and their entries.
include:
Expand Down
10 changes: 10 additions & 0 deletions memory/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Project notes

Durable, non-obvious facts about this repo that aren't derivable from the code or git history. Keep each note short and factual; delete notes that go stale.

For this project these notes live **in the repo** (here), not in the global Claude auto-memory location — add new durable findings as files in this directory
and link them below.

- [ci-and-verification.md](ci-and-verification.md) — how the site builds/deploys and how to verify changes (incl. the rtk/prettier gotcha)
- [csp-policy.md](csp-policy.md) — the meta-tag CSP: what it allows and why, and what can't be enforced this way
- [seo-and-metadata.md](seo-and-metadata.md) — SEO/metadata decisions (German-only, no JSON-LD, canonical/feed)
35 changes: 35 additions & 0 deletions memory/ci-and-verification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# CI, build, and verification

## How the site is built and deployed

- Deployed via **GitHub Actions** (`.github/workflows/jekyll.yml`) running `bundle exec jekyll build`, **not** the `github-pages` gem (it is commented out in
the `Gemfile`). Consequence: the site is **not** restricted to the GitHub Pages plugin allowlist — any Jekyll plugin listed in the `Gemfile` + `_config.yml`
`plugins:` works. (The old `whitelist:` block was removed as dead config.)
- PRs are gated by `.github/workflows/ci.yml`: build → PurgeCSS → html-proofer → pa11y (non-blocking) → prettier. `_site/` is built during CI, so anything that
scans the repo (like prettier) must ignore `_site/` via `.prettierignore` — do **not** pass `--ignore-path ''`, which disables it.

## `.ruby-version` must be fully qualified

rbenv does exact-string matching on `.ruby-version`. A bare minor like `3.4` fails with `rbenv: version '3.4' is not installed` and blocks every `ruby`/`bundle`
command. Use the installed patch version (e.g. `3.4.9`). `ruby/setup-ruby` in CI accepts the fully-qualified value fine.

## Verifying prettier: bypass the rtk proxy

An `rtk` shell hook in this environment intercepts `prettier` invocations and can print a **masked/canned result** (e.g. "All files formatted correctly") while
the real prettier is reporting failures. This caused false "prettier passes locally" claims that CI then failed on. When verifying formatting, run it through
the proxy bypass: `rtk proxy npx prettier --check .`. Treat CI green as the ground truth.

## Bundler / rubygems networking

`bundle update`/`install` fetches from `index.rubygems.org` (a CNAME to Fastly). Some local resolvers (e.g. a consumer router's DNS forwarder) fail to resolve
that specific hostname while resolving the `rubygems.org` apex fine — symptom is "Could not reach host index.rubygems.org" even though the network is up. Fix on
the machine: point DNS at a public resolver (1.1.1.1 / 8.8.8.8). Not a repo problem.

## New top-level files leak into the published site

Jekyll publishes any top-level file or directory that is not in the `_config.yml` `exclude:` list. Dotfiles/dot-dirs are auto-excluded, but plain-named ones are
NOT. This repo had `README.MD`, `package.json`, `package-lock.json`, `scripts/`, `purgecss.config.js`, and a `memory/` dir all being served publicly (e.g.
`https://www.inoeg.de/package.json` was live) until they were added to `exclude:`.

Rule: whenever you add a plain-named top-level file or directory that is repo tooling / internal docs (not site content), add it to the `exclude:` list in
`_config.yml`, then confirm it is absent from `_site/` after a build.
24 changes: 24 additions & 0 deletions memory/csp-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Content-Security-Policy (delivered via meta tag)

The site has no HTTP-header control (GitHub Pages), so the CSP is a `<meta http-equiv="Content-Security-Policy">` in `_includes/head.html`.

Current policy:

```
default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline';
font-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'
```

Why each non-obvious part is there:

- **`style-src 'unsafe-inline'`** — several Webflow-exported pages (`_pages/projekte.html`, `_pages/offenerbrief-epa-2025.html`) use inline `style=` attributes.
Removing it breaks their layout; a CSP that breaks styling is worse than none.
- **`font-src 'self' data:`** — `webflow.css` embeds an icon font as a `data:application/x-font-ttf` URI. Without `data:` the font is blocked (was a real
console error after the CSP first shipped).
- **`img-src 'self' data:`** — same reason (data-URI images in the CSS).

Cannot be enforced via a meta tag (header-only — omitted to avoid console noise / false sense of security): `X-Content-Type-Options`, `X-Frame-Options`, and CSP
`frame-ancestors`. Enforcing those needs a header-capable front (e.g. Cloudflare).

Implication: **do not add inline `<script>`** (including `<script type="application/ld+json">`) — `default-src 'self'` blocks it, and there is no `script-src`
allowance. See [[seo-and-metadata]] for why JSON-LD / `jekyll-seo-tag` were deliberately not adopted.
18 changes: 18 additions & 0 deletions memory/seo-and-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# SEO and metadata decisions

## German-only

The site is German-only. The `og:locale:alternate en_US` signal was removed and the `<html lang>` fallback set to `de` — do not re-introduce English alternate
signals or `hreflang` unless real English content is added. (One known inconsistency still open: `_pages/404.md` `title`/`excerpt` are English.)

## No JSON-LD / no jekyll-seo-tag

Structured data (`Organization` JSON-LD) and the `jekyll-seo-tag` plugin were **deliberately not adopted** because both require an inline
`<script type="application/ld+json">`, which the strict CSP (`default-src 'self'`, no `script-src`) blocks. The SEO payoff for a small brochure site did not
justify weakening the CSP with `'unsafe-inline'` or a brittle script hash. See [[csp-policy]]. Reopen only if the CSP is relaxed.

## What is in `_includes/head.html`

Hand-written (not plugin-generated): title, OG/Twitter tags, `<meta name=description>`, canonical (`<link rel="canonical">`), and feed autodiscovery via
`{% feed_meta %}` (jekyll-feed). The default social-share image is driven by `site.og_image_default` (empty by default → no image tag emitted, no broken
reference); per-page override via the `og_image` front-matter key.
Loading