Reformat nuxt-app and gate formatting in CI - #242
Conversation
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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
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-appusing 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/.editorconfigwith.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
speakersis 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.
| import { useDirectus } from '~/composables/useDirectus' | ||
| import type { H3Event } from 'h3' | ||
|
|
||
| import { defineEventHandler, getHeader } from 'h3' |
There was a problem hiding this comment.
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 mechanical — npm 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.
| interface DeepgramTranscriptResponse { | ||
| results: { | ||
| utterances: [{ | ||
| transcript: string, | ||
| speaker: string, | ||
| words: [ | ||
| { | ||
| punctuated_word: string, | ||
| start: number, | ||
| speaker: string, | ||
| } | ||
| ] | ||
| }] | ||
| } | ||
| results: { | ||
| utterances: [ | ||
| { | ||
| transcript: string |
There was a problem hiding this comment.
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 nestedwords: [, andspeakers: [{...}]are all onmainalready (lines 170, 173, 190), and the diff fortypes/items.tscontains no+/-on those declarations. Prettier cannot turnT[]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 literal1.
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> |
There was a problem hiding this comment.
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
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
mainwas 90 files away from its own committed Prettier config. Phase 2 tookprettier3.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.
Reformat nuxt-app with Prettier 3.9npm run prettier, zero hand editsGate formatting in CI…prettier:check+ the CI step, editor config fix, deprecated option removed--checkwrites nothing and exits non-zero. The existingprettierscript 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,typecheckandbuildby 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:
data-v-*, keyframe names, per-build uuid)Class order is masked deliberately and safely: the CSS cascade resolves by stylesheet order and specificity, never by token order in a
classattribute. 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
The source line was over 120 chars, so Prettier broke it and
Regulärlanded 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>madeinline-blockby 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:>Regulär<> Regulär <inline-blocksiblingsThe 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 withroot = 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/.editorconfigsaidindent_size = 2while.prettierrcsaystabWidth: 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, plusmax_line_length = 120to matchprintWidth.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-verifyand 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.mainhas no branch protection.GET /repos/programmierbar/website/branches/main/protectionreturns404 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:checkclean ·npm test91 passing / 10 files ·npm run lint0 errors, 127 warnings · typecheck ratchet steady at 263 ·SKIP_PRERENDER_ROUTE_DISCOVERY=true npm run buildexit 0.🤖 Generated with Claude Code
https://claude.ai/code/session_01JkKMceYAzAYLrSyC42FWTf