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
4 changes: 4 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist/
node_modules/
playwright-report/
test-results/
74 changes: 74 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
module.exports = {
root: true,
env: {
node: true,
browser: true,
},
extends: [
'plugin:vue/essential',
'@vue/airbnb',
'@vue/typescript/recommended',
],
parserOptions: {
ecmaVersion: 2020,
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',

// Git normalizes EOLs on checkout, so working copies are CRLF on Windows
// and LF elsewhere. Enforcing either one breaks lint on half the machines.
'linebreak-style': 'off',

'max-len': ['error', {
code: 120,
ignoreUrls: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignoreRegExpLiterals: true,
}],

// Vue 2 deliberately skips reactivity for keys beginning with `_`.
// SelectStep relies on that to hold large track arrays without paying the
// cost of making every element reactive, so the prefix is load-bearing.
'no-underscore-dangle': 'off',

// Scrobbling is intentionally sequential: Last.fm rate-limits aggressively
// and each request must settle (and its backoff elapse) before the next.
'no-await-in-loop': 'off',

// `new Promise((r) => setTimeout(r, ms))` is the sleep idiom used for
// rate-limit backoff throughout the Last.fm client.
'no-promise-executor-return': 'off',

'no-plusplus': 'off',
'no-continue': 'off',
'class-methods-use-this': 'off',

// airbnb bans for..of wholesale; it is used throughout for readable
// iteration. The genuinely hazardous constructs stay banned.
'no-restricted-syntax': ['error', 'ForInStatement', 'LabeledStatement', 'WithStatement'],

// The base rule flags TypeScript parameter properties as useless
// constructors; the TS-aware version understands they declare fields.
'no-useless-constructor': 'off',
'@typescript-eslint/no-useless-constructor': 'error',

// Views are legitimately single-word (Home, About, Scrobble, Scrobblify).
'vue/multi-word-component-names': 'off',

// Vuetify data tables address slots with dotted names (`item.artist`),
// which the rule reads as modifiers.
'vue/valid-v-slot': ['error', { allowModifiers: true }],
},
overrides: [
{
// Playwright specs and the dev-mock harness run in Node, not the browser,
// and legitimately import from devDependencies.
files: ['tests/**/*.{js,ts}', '*.config.js', '.eslintrc.js'],
rules: {
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
},
},
],
};
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ on:
branches: [master]

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: npm
- run: npm ci
# --no-fix: `npm run lint` auto-fixes, which would let CI silently pass.
- run: npm run lint:check

build:
name: Build
runs-on: ubuntu-latest
Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ on:
branches: [master]

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: npm
- run: npm ci
# --no-fix: `npm run lint` auto-fixes, which would let CI silently pass.
- run: npm run lint:check

build:
name: Build
runs-on: ubuntu-latest
Expand Down
6 changes: 3 additions & 3 deletions .postcssrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}
autoprefixer: {},
},
};
15 changes: 15 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,18 @@ Network failures emit `scrobble_network_error`.
Use the helpers in `src/services/Analytics.ts` (`trackEvent`, `trackError`,
`identifyUser`) rather than calling `posthog` directly. Analytics must never
break the app: every call is wrapped in try/catch and ignored on failure.

## Linting

`npm run lint` auto-fixes; `npm run lint:check` (`--no-fix`) is what CI runs, so
use that to see what CI will see. The config is ESLint + `@vue/airbnb` +
`@vue/typescript`.

Several airbnb rules are switched off in `.eslintrc.js` because they fight
patterns this codebase uses on purpose — each has a comment explaining why.
The load-bearing one: **`no-underscore-dangle` is off because Vue 2 skips
reactivity for keys starting with `_`**, which is how `SelectStep` holds large
track arrays cheaply. Renaming those fields would silently make them reactive
and tank performance on big histories.

Warnings (mostly `no-explicit-any`) do not fail the build; only errors do.
14 changes: 7 additions & 7 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module.exports = {
"presets": [
presets: [
[
"@vue/app",
'@vue/app',
{
"useBuiltIns": "entry"
}
]
]
}
useBuiltIns: 'entry',
},
],
],
};
Loading
Loading