Skip to content

Reformat nuxt-app and gate formatting in CI - #242

Merged
Jan0707 merged 4 commits into
mainfrom
prettier-sweep-and-format-gate
Aug 4, 2026
Merged

Reformat nuxt-app and gate formatting in CI#242
Jan0707 merged 4 commits into
mainfrom
prettier-sweep-and-format-gate

Conversation

@Jan0707

@Jan0707 Jan0707 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Two halves of one problem, in separate commits. Follows #241, whose plugin version this sweep's output depends on — rebased onto it after it merged.

The problem

main was 90 files away from its own committed Prettier config. Phase 2 took prettier 3.2.5 → 3.9.6, the new version formats slightly differently, nobody ran --write, and no CI step looks at formatting. Eight phases later, nothing had noticed.

Measured as Prettier-core drift, not plugin drift — the count is identical with the Tailwind plugin removed.

commit what
Reformat nuxt-app with Prettier 3.9 90 files, npm run prettier, zero hand edits
Gate formatting in CI… prettier:check + the CI step, editor config fix, deprecated option removed

--check writes nothing and exits non-zero. The existing prettier script would "pass" by mutating the tree — the same trap the ESLint step already documents.

Verifying a 90-file reformat, when no gate can see one

All four gates pass, and that is close to meaningless here: a formatter change is invisible to lint, test, typecheck and build by construction. So the real check was a rendered-output comparison — build before, build after, fetch 11 routes from a local production server, diff.

Four detectors were built and thrown away before one worked:

attempt outcome
strip all whitespace useless — counts quote/semicolon normalisation as content (63 files)
collapse template whitespace useless — attribute reflow inside tags reads as a change (46 files)
raw HTML diff 11/11 differ, entirely from source-derived hashes (chunk names, data-v-*, keyframe names, per-build uuid)
mask those + sort class tokens 10/11 byte-identical, 1 real difference

Class order is masked deliberately and safely: the CSS cascade resolves by stylesheet order and specificity, never by token order in a class attribute. Sorting still catches a class added or removed, since that changes the multiset. Each mask narrows sensitivity, so the script asserts it can still see an added class, a removed class, a changed attribute and inline whitespace.

The one real difference

-<div class="mb-1 text-xs font-bold uppercase tracking-widest text-lime">Regulär</div>
+<div class="mb-1 text-xs font-bold uppercase tracking-widest text-lime"> Regulär </div>

The source line was over 120 chars, so Prettier broke it and Regulär landed on its own line; Vue condenses that to a single leading/trailing space.

This is the failure mode worth fearing in a mass reformat — Prettier judges whitespace significance from a tag's default display, so a <div> made inline-block by a Tailwind class is invisible to it, and whitespace added between two such siblings is a visible gap. So I measured it in Chromium against the real compiled stylesheet:

geometry
>Regulär< width 600, height 16
> Regulär < width 600, height 16 — identical
control: same edit between two inline-block siblings shifts 4.12px

The control is the point — without it, "identical" is indistinguishable from a test that cannot see whitespace at all.

Lint also went 128 → 127 warnings. Chased rather than shrugged at: it is vue/first-attribute-linebreak, a formatting rule that Prettier's output now satisfies. Every other rule count is unchanged, so nothing was suppressed.

A correction inside this PR

I first added a root .editorconfig, then deleted it as dead code. Two already existed — nuxt-app/ and the Directus extension — both with root = true, which stops the upward search, so a root-level file is never read for either tree. My premise came from checking only the repo root.

The real bug was in the file that is read: nuxt-app/.editorconfig said indent_size = 2 while .prettierrc says tabWidth: 4. Editors read both, so they indented to 2 and the formatter immediately widened to 4. That is a fair summary of how this drift survived eight phases — the tooling disagreed with itself and nothing arbitrated. Fixed to 4, plus max_line_length = 120 to match printWidth.

No pre-commit hook, deliberately

It is the conventional answer and it does not earn its keep here: two active humans, essentially everything arrives via PR where CI already runs, and hooks are bypassable with --no-verify and absent in fresh clones — including the agent-authored commits, which are a real share of this repo's history. The CI check would still be needed, so a hook means two mechanisms for one guarantee. Worth revisiting if "CI red for a trailing comma" becomes a recurring annoyance.

⚠️ One thing this PR cannot fix

main has no branch protection. GET /repos/programmierbar/website/branches/main/protection returns 404 Branch not protected, so none of the checks — lint, test, typecheck:ratchet, build, or the new format check — actually blocks a merge. They go red and rely on someone noticing.

Phase 0 made upgrades detectable, which it achieved. It did not make a broken upgrade unmergeable, and the plan had been treating that as settled. Needs a repo admin (Settings → Branches → require status checks), like installing Renovate.

Gates

prettier:check clean · npm test 91 passing / 10 files · npm run lint 0 errors, 127 warnings · typecheck ratchet steady at 263 · SKIP_PRERENDER_ROUTE_DISCOVERY=true npm run build exit 0.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JkKMceYAzAYLrSyC42FWTf

Jan0707 and others added 3 commits August 4, 2026 15:07
Mechanical. `npm run prettier`, no hand edits.

Phase 2 took prettier 3.2.5 -> 3.9.6 and nobody ran --write afterwards, so
90 files drifted from the committed config over eight phases. Nothing in CI
checks formatting, which is why it went unnoticed; the next commit adds that
check.

Kept as its own commit so the gate and the editor docs stay reviewable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkKMceYAzAYLrSyC42FWTf
Adds `prettier:check` and runs it in the nuxt-app job. --check writes
nothing and exits non-zero; the existing `prettier` script would "pass" by
mutating the working tree, which is the same trap the ESLint step already
documents.

Without this, formatting is voluntary — and voluntary is how 90 files
drifted after Phase 2's prettier 3.2.5 -> 3.9.6 with nothing noticing.

nuxt-app/.editorconfig said indent_size = 2 while .prettierrc says
tabWidth: 4. Editors read both, so they indented at 2 and the formatter
immediately widened to 4. Corrected, plus max_line_length to match
printWidth. A root .editorconfig is not the fix: nuxt-app and the Directus
extension both set root = true, so a file above them is never read.

No pre-commit hook. Everything here arrives via PR where CI runs, hooks are
bypassable and absent in fresh clones (including agent commits), so a hook
would be a second mechanism for one guarantee.

Also drops the deprecated jsxBracketSameLine, which printed a warning on
every run. Verified inert: removing it changed no file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkKMceYAzAYLrSyC42FWTf
Documents how a 90-file reformat was verified, given that none of the gates
can see a formatter change: 11 routes fetched from local production builds
before and after, 10 byte-identical once source-derived hashes were masked
and class tokens sorted.

Four detectors were built and discarded first; each mask narrows
sensitivity, so the final one asserts it can still see an added class, a
removed class, a changed attribute and inline whitespace. The single real
difference (a line break putting text on its own line) was measured in
Chromium with a positive control that shifts 4.12px.

Also records two corrections to claims made earlier in this document: that
"sweep after stripe" had the order backwards, and that "this repo has no
.editorconfig" came from checking only the repo root when two existed.

And that main has no branch protection, so no CI check has ever blocked a
merge. Phase 0 made upgrades detectable, not unmergeable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkKMceYAzAYLrSyC42FWTf
Copilot AI lite review requested due to automatic review settings August 4, 2026 13:07
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
programmierbar-website Ready Ready Preview Aug 4, 2026 2:02pm

Request Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR brings the nuxt-app tree back in sync with its committed Prettier configuration and makes formatting regressions detectable by CI. It also aligns editor defaults (.editorconfig) with Prettier so “editor vs formatter” churn doesn’t reintroduce drift over time.

Changes:

  • Reformat a large portion of nuxt-app using the current Prettier/tooling output (mechanical whitespace/import/class formatting changes).
  • Add a non-mutating formatting gate (prettier:check) and enforce it in CI.
  • Align nuxt-app/.editorconfig with .prettierrc (indent + line length) and remove a deprecated Prettier option.

Reviewed changes

Copilot reviewed 90 out of 96 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
nuxt-app/types/search.ts Mechanical formatting of type definitions.
nuxt-app/types/items.ts Mechanical formatting of shared item/transcript types.
nuxt-app/server/utils/stripe.ts Mechanical formatting of Stripe helpers.
nuxt-app/server/utils/schema.ts Mechanical formatting of Zod schemas.
nuxt-app/server/utils/redirects.ts Import ordering only.
nuxt-app/server/middleware/rating.ts Mechanical formatting only.
nuxt-app/server/middleware/cocktails.ts Mechanical formatting/import ordering (middleware unchanged).
nuxt-app/server/api/vote.post.ts Mechanical formatting (incl. import ordering / ternary formatting).
nuxt-app/server/api/tickets/webhook.post.ts Import ordering only.
nuxt-app/server/api/tickets/create-checkout.post.ts Mechanical formatting of pricing logic and imports.
nuxt-app/server/api/speaker-portal/validate.get.ts Mechanical formatting of error message wrapping.
nuxt-app/server/api/email.post.ts Mechanical formatting (semicolon removal / indentation).
nuxt-app/server/api/checkin/stats.get.ts Mechanical formatting of error construction.
nuxt-app/server/api/checkin/scan.post.ts Mechanical formatting of error construction.
nuxt-app/README.md Markdown list formatting cleanup.
nuxt-app/plugins/vue-json-pretty.js Quote style formatting.
nuxt-app/pages/ticket-portal.vue Template/script formatting and import ordering.
nuxt-app/pages/suche.vue Template/script formatting and import ordering.
nuxt-app/pages/speaker-portal.vue Template/script formatting and minor wrapping.
nuxt-app/pages/profiles/[slug].vue Template/script formatting and import ordering.
nuxt-app/pages/meetup/index.vue Template/script formatting and wrapping.
nuxt-app/pages/meetup/[slug].vue Template/script formatting and import ordering.
nuxt-app/pages/konferenz/index.vue Template/script formatting and import ordering.
nuxt-app/pages/konferenz/[slug]/tickets/success.vue Template/script formatting and wrapping.
nuxt-app/pages/konferenz/[slug]/tickets/index.vue Template formatting (single-line paragraph).
nuxt-app/pages/kommt-bald.vue Template/script formatting and quote consistency.
nuxt-app/pages/hall-of-fame/[slug].vue Template/script formatting and wrapping.
nuxt-app/pages/aufnahmen.vue Mechanical indentation only.
nuxt-app/pages/api/cocktails.vue Template/script formatting and spacing.
nuxt-app/pages/agb.vue Mechanical indentation only.
nuxt-app/pages/admin/checkin.vue Template formatting and minor expression wrapping.
nuxt-app/package.json Add prettier:check script.
nuxt-app/helpers/trackGoal.ts Mechanical formatting only.
nuxt-app/helpers/jsonLdGenerator.ts Mechanical formatting only.
nuxt-app/helpers/ipProcessing.ts Mechanical formatting only.
nuxt-app/helpers/getMetaInfo.ts Mechanical formatting only.
nuxt-app/helpers/getAssetUrl.ts Mechanical formatting only.
nuxt-app/helpers/detectOS.ts Mechanical formatting only.
nuxt-app/helpers/buildSpeakerNamesForTalk.ts Mechanical formatting only.
nuxt-app/helpers/aiSpamFilter.ts Mechanical formatting only.
nuxt-app/composables/useWeightedRandomSelection.ts Mechanical formatting only.
nuxt-app/composables/useVideoConsent.ts Mechanical formatting only.
nuxt-app/composables/useTicketCheckoutStore.ts Mechanical formatting only.
nuxt-app/composables/usePodcastPlayer.ts Mechanical formatting only.
nuxt-app/composables/useFlashMessage.ts Mechanical formatting only.
nuxt-app/components/tickets/TicketStepReview.vue Template/script formatting and wrapping.
nuxt-app/components/tickets/TicketStepQuantity.vue Template/script formatting and wrapping.
nuxt-app/components/tickets/TicketStepBilling.vue Template/script formatting and wrapping.
nuxt-app/components/tickets/TicketStepAttendees.vue Template/script formatting and wrapping.
nuxt-app/components/tickets/TicketPurchaseFlow.vue Import ordering only.
nuxt-app/components/tickets/TicketPricingSummary.vue Import ordering and minor class ordering.
nuxt-app/components/TestimonialSlider.vue Template/script formatting and minor expression simplification.
nuxt-app/components/TalkItem.vue Mechanical formatting of template/script.
nuxt-app/components/SpeakerListItem.vue Template/script formatting and import ordering.
nuxt-app/components/SocialNetworks.vue Import ordering and formatting of config imports.
nuxt-app/components/SearchResultCard.vue Template/script formatting and wrapping.
nuxt-app/components/PrimaryPbButton.vue Class string formatting (trim trailing space).
nuxt-app/components/PrettyJSON.vue Template/script/style formatting and quote consistency.
nuxt-app/components/PodcastTranscript.vue Template/script formatting and wrapping.
nuxt-app/components/PodcastSlider.vue Minor expression simplification (ternaries).
nuxt-app/components/PodcastRating.vue Template/script/style formatting and wrapping.
nuxt-app/components/PodcastPlayer.vue Condense constant initialization formatting.
nuxt-app/components/PodcastBanner.vue Mechanical indentation only.
nuxt-app/components/Pagination.vue Import ordering and class object formatting.
nuxt-app/components/PageCoverImage.vue Template/script/style formatting only.
nuxt-app/components/NewsTicker.vue Template/script formatting and arrow function wrapping.
nuxt-app/components/MouseCursor.vue Reduce callback wrapping for readability (formatting).
nuxt-app/components/MemberCard.vue Template/script formatting only.
nuxt-app/components/MeetupStartAndEnd.vue Import ordering only.
nuxt-app/components/MeetupSection.vue Template/script formatting and wrapping.
nuxt-app/components/MeetupCalendarAndMaps.vue Formatting only.
nuxt-app/components/IndividualPlatforms.vue Template/script formatting and wrapping.
nuxt-app/components/GenericLazyList.vue Minor expression formatting changes.
nuxt-app/components/FaqList.vue Template/script formatting and quote consistency.
nuxt-app/components/FaqItem.vue Template/script formatting and quote consistency.
nuxt-app/components/DirectusImage.vue Import ordering only.
nuxt-app/components/ConferenceTickets.vue Template/script formatting and quote consistency.
nuxt-app/components/ConferenceTicketRed.vue SVG/template formatting only.
nuxt-app/components/ConferenceTicketLime.vue SVG/template formatting only.
nuxt-app/components/ConferenceTicketBlue.vue SVG/template formatting only.
nuxt-app/components/ConferenceSpeakersSlider.vue Template/script formatting and quote consistency.
nuxt-app/components/ConferenceSpeaker.vue Template/script/style formatting and wrapping.
nuxt-app/components/ConferenceSection.vue Template/script formatting and wrapping.
nuxt-app/components/ConferenceGallery.vue Template/script/style formatting and wrapping.
nuxt-app/components/ConferenceCover.vue Mechanical formatting only.
nuxt-app/components/ConferenceCard.vue Import ordering only.
nuxt-app/components/BackgroundSpotlights.vue Template/script formatting and quote consistency.
nuxt-app/.prettierrc Remove deprecated jsxBracketSameLine option.
nuxt-app/.editorconfig Align editor settings with Prettier (indent/line length) and add guidance comments.
docs/dependency-upgrade-plan.md Document the formatter sweep + CI gate in the upgrade plan.
AGENTS.md Document formatter expectations and how to keep editor/Prettier aligned.
.github/workflows/run_tests.yml Add CI formatting check step (npm run prettier:check) for nuxt-app.
Suppressed comments (1)

nuxt-app/types/items.ts:193

  • speakers is currently typed as a single-element tuple ([{ ... }]), which implies there is always exactly one speaker entry. It should be a normal array type.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to 3
import { useDirectus } from '~/composables/useDirectus'
import type { H3Event } from 'h3'

import { defineEventHandler, getHeader } from 'h3'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct that it is unused — one occurrence in the file, no type annotation using it — but not something this PR should change.

It is pre-existing: import type { H3Event } from 'h3' is on main today, and the sweep only moved the line (the sort-imports plugin put useDirectus above it). The diff contains no +/- for that import.

It is also already counted. It is one of the 14 @typescript-eslint/no-unused-vars warnings in the tracked 127, which Phase 5 deliberately demoted to warnings rather than fixing, because the fixes are a typing project rather than a lint upgrade. That burn-down is logged in docs/dependency-upgrade-plan.md.

The specific reason to keep it out of here: the reformat commit's only claim to being reviewable is that it is mechanicalnpm run prettier, zero hand edits, reproducible by running the same command. One hand-edited line costs that property for a warning that is already on a list.

Comment thread nuxt-app/types/items.ts
Comment on lines 169 to +173
interface DeepgramTranscriptResponse {
results: {
utterances: [{
transcript: string,
speaker: string,
words: [
{
punctuated_word: string,
start: number,
speaker: string,
}
]
}]
}
results: {
utterances: [
{
transcript: string

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, this is a real defect — and it is pre-existing, so I have logged it rather than fixing it here.

Confirmed both parts:

  • Not caused by the reformat. utterances: [{, the nested words: [, and speakers: [{...}] are all on main already (lines 170, 173, 190), and the diff for types/items.ts contains no +/- on those declarations. Prettier cannot turn T[] into [T].
  • Harmless today. The only consumer is helpers/prepareTranscript.ts:32, which calls .forEach — that compiles fine against a tuple and iterates all N elements at runtime. It would bite the first person to index past [0] or read .length, which TypeScript narrows to the literal 1.

You also spotted the third instance correctly: DirectusTranscriptItem.speakers has the same shape, and that one is read more widely than the Deepgram response is.

Logged in docs/dependency-upgrade-plan.md under "Small, verified, uncontroversial", with the note that the fix wants a typecheck-ratchet run alongside it, since these types feed the transcript path and the baseline is currently 263.

Keeping it out of this PR for the same reason as the unused import: the reformat commit is worth trusting only because it is purely mechanical.

<NuxtLink to="/login" class="mt-4 inline-block text-lime hover:text-blue">
Zum Login
</NuxtLink>
<NuxtLink to="/login" class="mt-4 inline-block text-lime hover:text-blue"> Zum Login </NuxtLink>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked this one carefully, because it is exactly the failure mode I went looking for in this PR — and it does not apply here: the spaces were already in the rendered output before the sweep.

Vue's default whitespace: 'condense' condenses a leading/trailing newline-plus-indent inside a non-empty text node to a single space. So both forms produce the same text node. Compiled both through @vue/compiler-dom:

before (multi-line): [" Zum Login "]
after  (one line):   [" Zum Login "]
identical: true

control (no spaces at all): ["Zum Login"]
extractor can distinguish: true

The control matters — my first attempt at this check used compile() instead of parse(), which transforms text nodes away, so it reported "identical" for two empty lists. That was a vacuous pass and I threw it out.

Prettier writing the explicit > Zum Login < on one line is it preserving what Vue already produced, not introducing anything.

You are right about the general hazard, though, and it did occur once elsewhere in this PR — in the opposite direction. On the conference page a 120+ char line got broken so text landed on its own line, turning >Regulär< into > Regulär <. I measured that one in Chromium: identical geometry, with a positive control confirming the measurement can see a 4.12px shift between two inline-block siblings. Details in the PR description.

Copilot caught a real inconsistency on #241: the sentence said four
packages were behind while the table below it listed six. Four needed a
decision; two were lockfile drift already inside their declared ranges.
Reworded so the text and the table agree.

Also logs a reviewer finding from #242 that is out of scope there:
types/items.ts types Deepgram's utterances, words and speakers as
single-element tuples rather than arrays. Pre-existing and untouched by the
reformat, and harmless today because the only consumer calls .forEach.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JkKMceYAzAYLrSyC42FWTf

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 90 out of 96 changed files in this pull request and generated no new comments.

@Jan0707
Jan0707 merged commit 3bde75d into main Aug 4, 2026
6 checks passed
@Jan0707
Jan0707 deleted the prettier-sweep-and-format-gate branch August 4, 2026 14:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants