Files
impuestospy/eslint.config.js
T
MichilisandClaude Opus 5 e4eb1617d1 phase-7: motion, an installable app, and a capture that survives no signal
GSAP carries the counter roll-ups, the bandeja card physics, the dialog
transitions and the three success moments FLOWS.md allows. Every one of
them checks prefers-reduced-motion first and does nothing when it is set.

boneyard and canvas-ui are not what SPEC.md's stack table says they are:
on npm the names belong to two abandoned projects that do neither job.
The skeletons were already ours; the two canvas spots are now sixty lines
each with no dependency. DECISIONS.md records the substitution.

The app installs, keeps a scan taken with no network in IndexedDB and
sends it when there is one, falls back to a page that explains itself,
and can push a deadline notice. Reading the log of what is queued is the
source of truth, so the notice clears when the capture actually lands.

The CSP now allows scripts by per-request nonce rather than by
'unsafe-inline'. That forced /offline to render per request: a
prerendered page carries a build-time nonce no live policy matches, so
its scripts were blocked and it never hydrated.

Two crashes fixed on the way. web-push throws on a VAPID subject that is
not https: or mailto:, and the code handed it APP_PUBLIC_URL, so any
machine with push keys died at boot; a misconfigured optional channel now
switches itself off and says why. And a subscription the push service
answers 410 for is deleted rather than retried forever.

Lighthouse on the production build: accessibility 100, best practices 96,
SEO 100, performance 73. The performance number is not trustworthy on
this machine and DECISIONS.md says why; total blocking time did fall from
17.6s to 1.7s once the hero canvas stopped drawing at full resolution
every frame and the landing page stopped importing GSAP.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 20:48:48 +00:00

130 lines
3.9 KiB
JavaScript

import js from '@eslint/js';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import globals from 'globals';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{
ignores: ['**/dist/**', '**/.next/**', '**/node_modules/**', '**/coverage/**', '**/*.d.ts'],
},
js.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
// `any` is allowed only with a written reason, per the build constraints.
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
'@typescript-eslint/consistent-type-imports': ['error', { fixStyle: 'inline-type-imports' }],
eqeqeq: ['error', 'always'],
'no-console': 'off',
},
},
// Module boundaries, SPEC.md section 4.
{
files: ['packages/**/*.ts'],
rules: {
'no-restricted-imports': [
'error',
{
patterns: [
{
group: ['**/apps/**', '@impuestos/api', '@impuestos/web'],
message:
'packages/* are pure and must not import from apps. Move the shared piece into a package.',
},
],
},
],
},
},
{
files: ['apps/api/src/**/*.ts'],
languageOptions: { globals: globals.node },
rules: {
'no-restricted-imports': [
'error',
{
patterns: [
{
// Only modules/pii may reach the pii tables. Everything else goes through
// the functions that module exports, so PII access stays auditable.
group: ['**/modules/pii/*', '!**/modules/pii/index'],
message:
'Import from modules/pii (its index) instead of reaching into the pii module.',
},
],
},
],
},
},
{
files: ['apps/api/src/modules/pii/**/*.ts', 'apps/api/src/db/**/*.ts'],
rules: { 'no-restricted-imports': 'off' },
},
// The web app has no database access at all: it holds no DB dependency and may not
// import one even transitively through a shared package.
{
files: ['apps/web/**/*.{ts,tsx}'],
languageOptions: {
globals: { ...globals.browser, ...globals.node },
parserOptions: { ecmaFeatures: { jsx: true } },
},
plugins: { react, 'react-hooks': reactHooks },
settings: { react: { version: 'detect' } },
rules: {
...reactHooks.configs.recommended.rules,
'no-restricted-imports': [
'error',
{
paths: [
{ name: 'kysely', message: 'The web app never touches the database.' },
{ name: 'better-sqlite3', message: 'The web app never touches the database.' },
{ name: 'pg', message: 'The web app never touches the database.' },
{
name: 'better-auth',
message: 'Auth server code lives in apps/api. Use better-auth/react here.',
},
],
},
],
// Constraint 12: user facing copy comes from the catalogs, never inline.
// Punctuation and separators are allowed so layout markup stays readable.
'react/jsx-no-literals': [
'error',
{
noStrings: true,
allowedStrings: ['·', '/', '|', ':', ',', '.', '-', '+', '(', ')', '%', '✓'],
ignoreProps: true,
},
],
},
},
// The service worker runs in its own global scope, not the window's.
{
files: ['apps/web/src/lib/service-worker.js'],
languageOptions: { globals: globals.serviceworker },
},
// Plain Node scripts: no JSX, no browser globals.
{
files: ['**/scripts/**/*.mjs', '*.config.{js,mjs,ts}', '**/*.config.{js,mjs,ts}'],
languageOptions: { globals: globals.node },
},
// Tests assert on real copy, so literals are the point there.
{
files: ['**/*.test.ts', '**/*.test.tsx', 'e2e/**/*.ts'],
rules: { 'react/jsx-no-literals': 'off' },
},
);