') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Restore the CRLF-default line-ending model and pin the Dockerfile by ptr727 · Pull Request #41 · ptr727/PhotoCleaner · GitHub
Skip to content

Restore the CRLF-default line-ending model and pin the Dockerfile - #41

Merged
ptr727 merged 1 commit into
developfrom
feature/restore-line-ending-model
Aug 3, 2026
Merged

Restore the CRLF-default line-ending model and pin the Dockerfile#41
ptr727 merged 1 commit into
developfrom
feature/restore-line-ending-model

Conversation

@ptr727

Copy link
Copy Markdown
Owner

.editorconfig and .gitattributes are intent-fidelity carries with intentRef: GOVERNANCE.md#line-endings, so the mechanical audit does not compare them. This repo had diverged from the hub's model, and one of the gaps has a live consequence.

Audit run 2026-08-03T16:52:36Z | hub 1ed0cc8, read against develop@39c896b.

The default was missing

The hub's model is a CRLF default with declared LF exceptions. This repo had dropped the [*]end_of_line = crlf default and enumerated CRLF per file type instead. Same result for the types it listed, but every other type had no declared ending at all:

PhotoCleaner.slnx, PhotoCleaner.code-workspace, LICENSE, .gitignore, .dockerignore, and .editorconfig itself.

Restoring the default covers them and makes the per-type CRLF lines redundant, so they go, along with [*.{json,jsonc}] and [*.{cmd,bat,ps1}], which carried nothing else.

Two missing LF pins, one that matters

  • Docker/Dockerfile had no pin in either file, although this repo ships one. A CRLF there breaks RUN heredocs and line continuations. It is LF in the tree today, so intent held by accident rather than by rule - the pin is what keeps it that way through a checkout or a git add --renormalize.
  • .husky/pre-commit is an extensionless shebang script matching no extension rule, so the restored CRLF default would have claimed it. .gitattributes already pinned it and .editorconfig did not - exactly the gap the default exposes.

.gitattributes was itself LF

The hub's is CRLF, and under the restored default an LF .gitattributes violates the repo's own rule, so it is rewritten as CRLF. Its comments regain the core.autocrlf, git add --renormalize ., and git ls-files --eol guidance the hub carries.

The hub's catalog/, .github/actions/, scripts/*.py, host-setup/, spec/*.py and uv.lock pins are hub-only paths and stay out.

Verification

  • editorconfig-checker passes over the whole tree under the new rules.
  • git add --renormalize . stages nothing beyond these two files. The new pins describe the tree exactly rather than proposing a conversion, which is the check that matters here - a wrong pin would show up as a mass re-ending.
  • git check-attr text eol -- Docker/Dockerfile now reports text: set / eol: lf, where it previously reported attr/-text.
  • git ls-files --eol shows LF only for the pinned set plus .github/workflows/**.
  • dotnet husky run clean, hub prose gate clean on the changed lines.

Third of four in the round. Follows #39 and #40.

🤖 Generated with Claude Code

The hub's model is a CRLF default with declared LF exceptions. This repo
had dropped the `[*]` `end_of_line = crlf` default and enumerated CRLF per
file type instead, which produces the same result for the types it listed
and leaves every other type with no declared ending at all: `.slnx`, the
`.code-workspace`, `LICENSE`, `.gitignore`, `.dockerignore`, and
`.editorconfig` itself were all uncovered. Restoring the default covers
them and makes the per-type CRLF lines redundant, so they go, along with
the `[*.{json,jsonc}]` and `[*.{cmd,bat,ps1}]` sections that carried
nothing else.
Two LF pins were missing, and one of them matters. `Docker/Dockerfile` had
no pin in either file although this repo ships one, and a CRLF there breaks
RUN heredocs and line continuations. It is LF in the tree today, so intent
held by accident rather than by rule; the pin is what keeps it that way
through a checkout or a renormalize. `.husky/pre-commit` is an
extensionless shebang script that matches no extension rule, so the CRLF
default would have claimed it. `.gitattributes` already pinned it and
`.editorconfig` did not, which is exactly the gap the restored default
exposes.
`.gitattributes` itself was LF while the hub's is CRLF, and under the
restored default that is a violation of the repo's own rule, so it is
rewritten as CRLF. Its comments regain the configure, renormalize, and
inspect commands the hub carries.
The hub's `catalog/`, `.github/actions/`, `scripts/*.py`,
`host-setup/`, `spec/*.py` and `uv.lock` pins are hub-only paths and stay
out.
Verified: `editorconfig-checker` passes over the whole tree, and
`git add --renormalize .` stages nothing beyond these two files, so the
new pins describe the tree exactly rather than proposing a conversion.
`git check-attr` confirms the Dockerfile pin resolves.
Audit run 2026-08-03T16:52:36Z, hub 1ed0cc8, against develop@39c896b.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings August 3, 2026 17:28
@codecov

codecovBot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 43.37%. Comparing base (39c896b) to head (b3768b7).

Additional details and impacted files
@@ Coverage Diff @@## develop #41 +/- ##
========================================
Coverage 43.37% 43.37% ========================================
Files 28 28 Lines 3896 3896 Branches 306 306 ========================================
Hits 1690 1690 Misses 2154 2154 Partials 52 52 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

CopilotAI left a comment

Copy link
Copy Markdown

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 restores the repository’s line-ending governance model by reintroducing a CRLF default in .editorconfig and explicitly pinning LF-only exceptions that must remain LF (notably Dockerfiles and execution-sensitive scripts/hooks). This aligns checkout/editor behavior with the intended “CRLF by default, LF by exception” approach and prevents accidental CRLF conversions that can break shell hooks and Dockerfile parsing.

Changes:

  • Restore [*] end_of_line = crlf in .editorconfig and remove redundant per-type CRLF declarations.
  • Add explicit LF pins for .husky/pre-commit and Dockerfiles in .editorconfig.
  • Add git-enforced LF pins for Dockerfiles in .gitattributes (alongside existing LF pins for shell scripts and the Husky hook).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

FileDescription
.gitattributesAdds git-enforced LF pinning for Dockerfiles and expands guidance comments while keeping * -text default behavior.
.editorconfigReintroduces CRLF as the default line ending and declares LF exceptions for workflows, shell scripts, Husky hook, and Dockerfiles.

@ptr727
ptr727 merged commit 788f7b7 into developAug 3, 2026
14 checks passed
@ptr727
ptr727 deleted the feature/restore-line-ending-model branch August 18, 2026 17:19
Sign up for freeto 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

@ptr727