Skip to content

feat: add ESLint, Prettier, and Stylelint for web apps#625

Merged
waynesun09 merged 7 commits into
mainfrom
add-frontend-linting
Jun 23, 2026
Merged

feat: add ESLint, Prettier, and Stylelint for web apps#625
waynesun09 merged 7 commits into
mainfrom
add-frontend-linting

Conversation

@waynesun09

@waynesun09 waynesun09 commented May 3, 2026

Copy link
Copy Markdown
Member

Summary

Establishes ESLint, Prettier, and Stylelint tooling for both web/docs and web/admin Svelte 5 apps. The primary focus is web/docs/src (documentation viewer), with web/admin/src included as scaffolding since active admin development is on hold.

  • Adds ESLint 9 (flat config) with eslint-plugin-svelte, typescript-eslint, and Svelte 5 component quality rules
  • Adds Prettier with prettier-plugin-svelte for consistent formatting
  • Adds Stylelint with stylelint-config-html/svelte for CSS linting in scoped styles
  • Integrates lint-staged into the existing pre-commit framework as a local hook — no new hook system, works alongside gitleaks, shellcheck, and other existing hooks
  • Configures per-app Svelte parser configs in ESLint (adminSvelteConfig / docsSvelteConfig) so each app can evolve independently
  • Formats existing codebase with Prettier in a separate commit for clean git blame
  • Adds 6 npm scripts: lint, lint:fix, format, format:check, stylelint, stylelint:fix
  • Fixes ESLint errors in web/docs: {#each} keys, {@html} suppression for build-time markdown
  • Fixes Stylelint errors in web/docs/src/app.css: selector ordering, range notation, empty lines
  • Removes dead scanComplete reactive state from OrgList.svelte

Scope

Linting covers both frontend apps:

  • web/docs/src/** — documentation viewer (primary focus)
  • web/admin/src/** — admin SPA (scaffolding, active development on hold)

Key rules enabled

Rule Level Purpose
svelte/no-at-html-tags error XSS prevention
svelte/require-each-key error Prevents rendering bugs in {#each}
svelte/block-lang error Enforces lang="ts" on scripts
@typescript-eslint/no-unused-vars error Dead code detection
svelte/max-lines-per-block warn Prevents monolithic script/template/style blocks
max-lines (150) warn Whole-file component size cap

Pre-commit integration

Uses the existing pre-commit framework (.pre-commit-config.yaml) instead of adding a separate hook system. The lint-staged hook runs ESLint, Stylelint, and Prettier with --fix on staged web/{admin,docs}/src/** files. Existing hooks (gitleaks, detect-private-key, go-vet, ADR linters, etc.) are unaffected.

Phased rollout

  • This PR (Phase 1): All size rules at warn, baseline disables on existing violations
  • Phase 2: Add lint steps to CI workflow (follow-up PR)
  • Phase 3: After component decomposition, upgrade to error and enable color-no-hex

Test plan

  • npm run format:check passes (0 issues)
  • npm run lint passes (0 errors, warnings only for existing large files)
  • npm run stylelint passes (0 issues)
  • npm run check passes (svelte-check)
  • CI passes: build, web, test, commit-lint, DCO, codecov
  • Verify no regressions in docs viewer or admin SPA functionality

@fullsend-ai-review

fullsend-ai-review Bot commented May 3, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [dependency-removal-breaks-consumer] package.json — The PR removes toml-eslint-parser from devDependencies, but cloudflare_site/scripts/patch-wrangler-rate-limit-namespace-ids.mjs (line 18) imports it with import { parseTOML } from "toml-eslint-parser". After this change, npm ci will no longer install the package and the CI script will fail with a module-not-found error at runtime. The cloudflare_site/ directory is in the ESLint/Prettier ignore lists but the script still depends on the npm package being installed.
    Remediation: Restore toml-eslint-parser in devDependencies.

  • [protected-path] .pre-commit-config.yaml — This PR modifies .pre-commit-config.yaml, which is a protected path requiring human approval. The change adds a lint-staged hook for web app files. There is no linked issue justifying the modification. The PR description explains the rationale (integrating lint-staged into the existing pre-commit framework), but protected-path changes require a linked issue for formal authorization.
    Remediation: File an issue documenting why .pre-commit-config.yaml needs modification (adding lint-staged hook for web app linting) and link it to this PR.

Low

  • [dead-code] web/admin/src/lib/auth/turnstile.ts:90 — The ESLint disable comment // eslint-disable-next-line preserve-caught-error references a rule that does not exist in the ESLint configuration (@eslint/js, typescript-eslint, or eslint-plugin-svelte). The disable directive is a no-op.

  • [ineffective-each-key] web/admin/src/routes/OrgList.svelte — Two {#each} blocks use array index (i) as the key. Index-based keys are functionally equivalent to having no key for Svelte DOM diffing. Same pattern in web/docs/src/lib/DocTreeNav.svelte lines 109 and 141. These satisfy svelte/require-each-key but provide no reconciliation benefit.

  • [scope-coherence] package.json — The PR bundles tooling setup with substantive code changes beyond auto-formatting: removal of unused scanComplete state, addition of {#each} keys, { cause: e } additions to error constructors, and unused variable renaming. These are likely linter-driven fixes, but the PR description should distinguish auto-fix results from manual improvements.

  • [missing-contributor-guidance] CONTRIBUTING.md:21 — CONTRIBUTING.md instructs developers to run make lint before pushing. While lint-staged now runs automatically via pre-commit for web files, the new npm-based linting scripts (lint, format, stylelint) for independent iteration are not mentioned.

  • [missing-contributor-guidance] web/README.md — web/README.md does not mention the newly added linting, formatting, and style-checking npm scripts. Developers working on the web SPAs may not discover these tools.

  • [missing-contributor-guidance] AGENTS.md:13 — AGENTS.md mentions "Always stage your changes before running make lint" but does not clarify that make lint now includes lint-staged for web files via the new pre-commit hook.


Labels: PR adds linting and formatting tooling infrastructure for web apps — maintenance/chore work.

Previous run

Review

Findings

High

  • [dependency-removal-breaks-consumer] package.json — The PR removes toml-eslint-parser from devDependencies, but cloudflare_site/scripts/patch-wrangler-rate-limit-namespace-ids.mjs (line 18) imports it with import { parseTOML } from "toml-eslint-parser". After this change, npm ci will no longer install the package and the CI script will fail with a module-not-found error at runtime. The package name contains "eslint" but it is actually a TOML parser used by a build script, not an ESLint plugin.
    Remediation: Keep toml-eslint-parser in devDependencies.

  • [protected-path] .pre-commit-config.yaml — This PR modifies .pre-commit-config.yaml, which is a protected path requiring human approval. There is no linked issue justifying the change. The PR description explains the rationale (integrating lint-staged into the existing pre-commit framework), but protected-path changes require a linked issue for formal authorization.
    Remediation: File an issue documenting why .pre-commit-config.yaml needs modification (adding lint-staged hook for web app linting) and link it to this PR.

Low

  • [scope-coherence] package.json — The PR bundles tooling setup with substantive code changes beyond auto-formatting: removal of unused scanComplete state, addition of {#each} keys, { cause: e } additions to error constructors, and unused variable renaming. These are likely linter-driven fixes, but the PR description should distinguish auto-fix results from manual improvements.

  • [dead-code] web/admin/src/lib/auth/turnstile.ts:99 — The ESLint disable comment // eslint-disable-next-line preserve-caught-error references a rule that does not exist in the ESLint configuration (@eslint/js, typescript-eslint, or eslint-plugin-svelte). The disable directive is a no-op.

  • [ineffective-each-key] web/admin/src/routes/OrgList.svelte — Two {#each} blocks use array index (i) as the key. Index-based keys are functionally equivalent to having no key for Svelte DOM diffing. Same pattern in web/docs/src/lib/DocTreeNav.svelte lines 109 and 141. These satisfy svelte/require-each-key but provide no reconciliation benefit.

  • [XSS - eslint rule bypass] web/docs/src/App.svelte:502 — An eslint-disable-next-line svelte/no-at-html-tags comment exempts the {@html page.html} usage from the XSS guard. The comment documents this as rendered markdown from a build-time pipeline, which is acceptable given rehype-sanitize is in the dependency chain.

  • [missing-contributor-guidance] CONTRIBUTING.md:21 — CONTRIBUTING.md instructs developers to run make lint before pushing. While lint-staged now runs automatically via pre-commit for web files, the new npm-based linting scripts for independent iteration are not mentioned.


Labels: PR adds linting and formatting tooling infrastructure — maintenance/housekeeping, not a user-facing feature.

Previous run (2)

Review

Findings

High

  • [protected-path] .pre-commit-config.yaml — This PR modifies .pre-commit-config.yaml, which is a protected path requiring human approval. There is no linked issue justifying the change. The PR description explains the rationale (integrating lint-staged into the existing pre-commit framework), but protected-path changes require a linked issue for formal authorization.
    Remediation: File an issue documenting why .pre-commit-config.yaml needs modification (adding lint-staged hook for web app linting) and link it to this PR.

Medium

  • [authorization] .pre-commit-config.yaml — No linked issue for this non-trivial PR. The PR introduces new tooling (ESLint, Prettier, Stylelint), modifies the pre-commit workflow, and includes substantive code changes across 49 files. Without a linked issue, there is no trace to authorized work. See also: [protected-path] finding above.
    Remediation: Create a GitHub issue documenting the justification for adding linting tooling to the web apps and link the PR to it.

  • [scope-coherence] package.json — The PR bundles tooling setup with substantive code changes beyond formatting: removal of unused scanComplete state, addition of {#each} keys, { cause: e } additions to error constructors, and unused variable renaming. While these are likely auto-fixes from the new linter, the PR description should clearly distinguish auto-fix results from manual improvements.
    Remediation: Document in the PR description which changes are auto-fixes from the linter vs. manual improvements, or split into separate commits.

Low

  • [dead-code] web/admin/src/lib/auth/turnstile.ts:99 — The ESLint disable comment // eslint-disable-next-line preserve-caught-error references a rule that does not exist in the ESLint configuration (js.configs.recommended, ts.configs.recommended, or svelte configs). The disable comment is a no-op and ESLint 9 will warn about unused disable directives.

  • [architectural-coherence] .pre-commit-config.yaml — The PR adds lint-staged as a local hook, creating a hybrid linting model where JS/TS uses lint-staged (configured in package.json) while all other languages use pre-commit's native hooks. This is a common pattern in polyglot repos but lacks documented rationale.

  • [ineffective-each-key] web/admin/src/routes/OrgList.svelte:591 — Four {#each} blocks use array index (i) as the key. Index-based keys are functionally equivalent to having no key for Svelte DOM diffing. These satisfy svelte/require-each-key but provide no reconciliation benefit. Same pattern in web/docs/src/lib/DocTreeNav.svelte lines 109 and 141.

  • [missing-developer-documentation] web/admin/README.md, web/docs/README.md — Neither README mentions the 6 newly added linting and formatting scripts (lint, lint:fix, format, format:check, stylelint, stylelint:fix). Developers working on the web SPAs may not discover these tools.

  • [missing-contributor-guidance] CONTRIBUTING.md:21 — CONTRIBUTING.md instructs developers to run make lint before pushing. While lint-staged now runs automatically via pre-commit for web files, the new npm-based linting scripts for independent iteration are not mentioned.


Labels: PR adds linting/formatting tooling for web frontend apps

Previous run (3)

Review

Findings

High

  • [protected-path] .pre-commit-config.yaml — This PR modifies .pre-commit-config.yaml, which is a protected path requiring human approval. There is no linked issue justifying the change. The PR description explains the rationale (integrating lint-staged into the existing pre-commit framework), but protected-path changes require a linked issue for formal authorization. Human approval is always required for protected-path changes regardless of context.
    Remediation: File an issue documenting why .pre-commit-config.yaml needs modification (adding lint-staged hook for web app linting) and link it to this PR.

Medium

  • [architectural-coherence] .pre-commit-config.yaml — The PR adds lint-staged as a local hook to .pre-commit-config.yaml. The repository uses pre-commit's native hooks for all other linting (ruff, gofmt, shellcheck, actionlint, etc.). Introducing lint-staged delegates to npm tooling within the pre-commit framework, creating a hybrid approach where JS/TS linting uses a different configuration model (lint-staged config in package.json) than all other languages.
    Remediation: Evaluate whether ESLint/Prettier/Stylelint can be configured as native pre-commit hooks (e.g., using mirrors-eslint) to maintain consistency, or document the rationale for the hybrid approach.

  • [missing-contributor-guidance] CONTRIBUTING.md — CONTRIBUTING.md instructs developers to run make lint before pushing. With the new lint-staged hook in .pre-commit-config.yaml, make lint / pre-commit run will now invoke ESLint/Prettier/Stylelint via lint-staged for web source files. However, CONTRIBUTING.md does not mention the new npm-based linting scripts (lint, format, stylelint) that developers can run independently for faster feedback.
    Remediation: Update CONTRIBUTING.md to document the new npm-based linting workflow for JavaScript/TypeScript/Svelte code.

Low

  • [dead-code] web/admin/src/lib/auth/turnstile.ts:89 — The ESLint disable comment // eslint-disable-next-line preserve-caught-error references a rule name that does not exist in the ESLint configuration. The comment is a no-op. Consider removing it or adding { cause: e } to the thrown Error directly, matching the pattern used in orgConfigParse.ts and githubClient.ts in this same PR.

  • [ineffective-each-key] web/admin/src/routes/OrgList.svelte:591 — The {#each} keys added for missingInstallRequirements and helpBullets use array index (i) as the key. Index-based keys are functionally equivalent to having no key for Svelte DOM diffing. Same pattern in web/docs/src/lib/DocTreeNav.svelte lines 109 and 141. These satisfy svelte/require-each-key but are no-ops.

  • [missing-developer-documentation] web/admin/README.md, web/docs/README.md — Neither README mentions the new linting and formatting scripts (lint, lint:fix, format, format:check, stylelint, stylelint:fix). Developers working on the web SPAs may not discover these tools.

Info

  • [code-quality-improvement] web/admin/src/routes/OrgList.svelte — Removal of the scanComplete state variable is correct — the variable was only ever assigned, never read. Good dead-code cleanup.
Previous run (4)

Review

Findings

High

  • [hook-framework-mismatch] .husky/pre-commit, package.json — The repo uses Python's pre-commit framework (.pre-commit-config.yaml, make lintpre-commit run, make bootstrappre-commit install). Adding Husky via .husky/pre-commit and a "prepare": "husky" script in package.json creates a parallel, conflicting hook system. Both frameworks write to .git/hooks/pre-commit — whichever is installed last overwrites the other, silently disabling the other's hooks. This means developers who run npm install will lose the existing pre-commit hooks (gitleaks, detect-private-key, Go vet, ADR linters), and developers who run pre-commit install will lose the lint-staged hook.
    Remediation: Remove .husky/pre-commit, the husky devDependency, and the "prepare": "husky" script from package.json. Instead, add a local hook entry to .pre-commit-config.yaml that runs the npm linting scripts (e.g., entry: npx lint-staged, language: system, scoped to web/ files). This keeps all hook configuration in the existing pre-commit framework.

Medium

  • [config-reference-inconsistency] eslint.config.js:6 — The ESLint config imports only ./web/admin/svelte.config.js but the lint script targets both web/admin/src/ and web/docs/src/. The svelteConfig is passed to parserOptions for all **/*.svelte files (lines 98–106), meaning web/docs Svelte files will be parsed using admin's Svelte config. If the two configs ever diverge (different preprocessors, compiler options), linting results for web/docs may be incorrect.
    Remediation: Either document why admin's config can be shared for ESLint parsing (if they are guaranteed identical), or use separate ESLint config blocks for web/admin/src and web/docs/src that reference their respective svelte.config.js files — matching the pattern in vite.config.ts.

  • [missing-contributor-guidance] CONTRIBUTING.mdCONTRIBUTING.md instructs developers to run make lint before pushing, but make lint calls pre-commit run, which has no hooks for ESLint, Prettier, or Stylelint. The new JS/TS linting tools are only triggered via Husky + lint-staged on commit. Contributors who skip npm ci (e.g., working only on Go code) won't have the hooks installed, and those who rely on make lint won't catch JS/TS linting issues.
    Remediation: Add guidance to CONTRIBUTING.md about the new web app linting workflow, or (preferably) integrate the JS/TS linting into the existing pre-commit framework so make lint covers everything.

Low

  • [unnecessary reactivity] web/docs/src/App.svelte:141SvelteURL (a reactive URL wrapper from svelte/reactivity) is used in syncRouteFromLocation, which is a purely imperative function. The URL object is created, mutated, converted to string via toString(), and discarded. Standard new URL() would be functionally identical and avoids importing the reactive wrapper unnecessarily.

  • [ineffective each-key] web/admin/src/routes/OrgList.svelte:627 — The {#each} keys added for missingInstallRequirements and helpBullets use array index (i) as the key (e.g., {#each items as line, i (i)}). Index-based keys provide no benefit for Svelte's DOM diffing — they behave identically to omitting a key. Same pattern in web/docs/src/lib/DocTreeNav.svelte lines 109 and 141. This satisfies the svelte/require-each-key lint rule but is a no-op.

  • [missing-developer-documentation] web/admin/README.md, web/docs/README.md — Neither README mentions the new linting and formatting scripts (lint, lint:fix, format, format:check, stylelint, stylelint:fix). Developers working on the web SPAs may not discover these tools.

Info

  • [nonexistent eslint rule] web/admin/src/lib/auth/turnstile.ts:87 — The eslint-disable-next-line preserve-caught-error comment references a rule that does not exist in the ESLint configuration or any configured plugin. ESLint will report an unused disable directive warning.

  • [ESLint security rules] eslint.config.js — The config enables svelte/no-at-html-tags: "error" as a default, which is good XSS prevention. The only suppression is in web/docs/src/App.svelte for build-time pipeline content with an explanatory comment.

  • [code-quality-improvement] web/admin/src/routes/OrgList.svelte — Removal of the scanComplete state variable is correct — it was only ever assigned, never read in any conditional or template expression. Good dead-code cleanup.

Previous run (5)

Review: #625

Head SHA: 9b15e3b
Timestamp: 2026-05-06T00:00:00Z
Outcome: approve

Summary

This PR introduces ESLint 9, Prettier, and Stylelint tooling for the admin SPA with a well-structured configuration. The vast majority of changes are automated Prettier reformats (whitespace, line-length adjustments) with no behavioral impact. A small number of substantive changes — removal of the unused scanComplete state variable, renaming an unused destructured appSlug to _appSlug, adding { cause: e } to two error constructors, adding {#each} keys, and modernizing deprecated CSS properties — are all correct and improve code quality. No security, correctness, or intent-alignment concerns were found.

Findings

Info

  • [style/conventions] web/admin/src/lib/auth/turnstile.tseslint-disable-next-line preserve-caught-error references a rule that does not exist in the ESLint config. The comment is a no-op but serves as a TODO marker. Consider using a plain // TODO: comment instead to avoid confusion about whether a real rule is being suppressed.

  • [correctness] web/admin/src/routes/OrgList.svelte — Two {#each} blocks use array index as key ({#each items as line, i (i)}). This satisfies svelte/require-each-key but provides no reordering benefit over keyless iteration. Acceptable here since missingInstallRequirements and helpBullets are static arrays, but worth noting if they ever become dynamic.

  • [correctness] web/admin/src/lib/layers/githubClient.ts:37, web/admin/src/lib/layers/orgConfigParse.ts:78 — Two error throws gained { cause: e }, preserving the error chain. This is a positive behavioral change beyond pure formatting.

  • [correctness] web/admin/src/routes/OrgList.svelte — Removal of the scanComplete state variable is correct; the variable was only ever written to and never read anywhere in the codebase.

  • [style/conventions] web/admin/src/routes/OrgList.svelte — CSS modernizations (word-break: break-word to overflow-wrap: break-word, clip: rect(...) to clip-path: inset(50%), rgba() to modern rgb() syntax) are all correct upgrades from deprecated properties.

Footer

Outcome: approve
This review applies to SHA 9b15e3b2b4e0127ec87ab37fe7b995683e01c6dc. Any push to the PR head clears this review and requires a new evaluation.

Previous run (6)

Review: #625

Head SHA: 5661747
Timestamp: 2026-05-06T00:00:00Z
Outcome: approve

Summary

This PR adds ESLint 9 (flat config), Prettier, and Stylelint tooling for the admin SPA, along with Husky + lint-staged for pre-commit hooks. The vast majority of the diff is Prettier auto-formatting of existing code (line-wrapping changes, blank lines between CSS rules). The few behavioral changes — adding { cause: e } to two error constructors, adding {#each} keys for svelte/require-each-key, renaming unused variables with _ prefix, and modernizing CSS (clip-path, overflow-wrap, rgb() modern syntax) — are all correct and low-risk. The ESLint config enables good security defaults (svelte/no-at-html-tags: error for XSS prevention, svelte/block-lang to enforce TypeScript). No correctness, security, or injection concerns found.

Findings

Medium

  • [Correctness] web/admin/src/routes/OrgList.sveltescanComplete (renamed _scanComplete) is a $state variable that is written in 7 places but never read anywhere in the template or script logic. This is dead state. The PR correctly silences the lint warning by prefixing with _, but the underlying question is whether this state was intended to drive UI (e.g. a loading indicator) and was accidentally omitted, or is genuinely unnecessary. Consider removing it entirely or wiring it up if it was meant to be used.
    Remediation: Audit whether scanComplete should gate any UI behavior; if not, remove the variable and all its assignments entirely rather than underscore-prefixing.

Low

  • [Correctness] web/admin/src/lib/orgs/fetchOrgs.ts:154 — Similar pattern: appSlug renamed to _appSlug inside the progress callback to suppress unused-var. The final appSlug is correctly captured from the completed fetch result later, so the intermediate one is indeed unused. This is correct but worth a comment explaining why the destructured value is intentionally discarded.

Info

  • [Style] web/admin/src/lib/auth/turnstile.ts:90 — The eslint-disable-next-line preserve-caught-error comment references a rule (preserve-caught-error) that does not appear in the ESLint config in this PR. If this rule doesn't exist, the disable comment is a no-op. This is harmless but may indicate a planned custom rule or a copy-paste artifact. Verify that this rule is actually active.

  • [Style] CSS modernizations (rgba()rgb(), clipclip-path, word-breakoverflow-wrap) are all correct and improve standards compliance. No browser compatibility concerns for the target audience (modern browsers).

  • [Correctness] The two { cause: e } additions in githubClient.ts:37 and orgConfigParse.ts:78 are good improvements that preserve error chains for debugging.

Footer

Outcome: approve
This review applies to SHA 5661747c811403409c02fe1e39e5bec9c33b5f4f. Any push to the PR head clears this review and requires a new evaluation.

Previous run (7)

Review: #625

Head SHA: 613f465
Timestamp: 2026-05-03T00:00:00Z
Outcome: request-changes

Summary

The linting and formatting tooling is well-structured and the Prettier/ESLint/Stylelint configuration is correct. However, introducing Husky v9 into a repo that already uses the Python pre-commit framework creates a hook-path conflict that silently disables security-critical pre-commit hooks (gitleaks, detect-private-key, bandit) on commit. This must be resolved before merge.

Findings

High

  • [Correctness / Platform Security] .husky/pre-commit + package.json ("prepare": "husky") — Husky v9 sets core.hooksPath=.husky/ on npm install, which redirects git from .git/hooks/ to .husky/. The repo already has a .pre-commit-config.yaml with security-relevant hooks (gitleaks, detect-private-key, bandit, actionlint, go-vet, ADR linters). After Husky installs, those hooks no longer run automatically on commit — only npx lint-staged runs. This silently degrades the repo's secret-detection and code-quality guardrails.
    Remediation: Either (a) chain both systems in .husky/pre-commit (e.g., pre-commit run --hook-stage pre-commit && npx lint-staged), (b) add frontend lint-staged as a local hook in .pre-commit-config.yaml and remove Husky entirely, or (c) document the interaction and ensure CI compensates. Option (b) is simplest since the repo already standardises on pre-commit.

Medium

  • [Correctness] web/admin/src/lib/auth/turnstile.ts:90 — The eslint-disable-next-line preserve-caught-error comment references a rule (preserve-caught-error) that does not exist in any of the installed ESLint plugins (@eslint/js, typescript-eslint, eslint-plugin-svelte). ESLint 10 reports unused disable directives at warn level by default, so this should surface as a warning — yet the PR claims 0 warnings. Verify the rule name is correct or remove the disable comment to avoid confusion.
    Remediation: Replace with a valid rule name (e.g., a custom rule planned for Phase 2) or remove the disable comment and add a plain // TODO: instead.

Info

  • [Style] eslint.config.js — The config is placed at the repo root but only lints web/admin/src/. This works correctly due to the ignores block and the files globs, but a comment at the top noting the intended scope (admin SPA only) would help future contributors understand why it lives at the root rather than under web/admin/.

  • [Correctness] web/admin/src/routes/Home.svelte:39word-break: break-word was changed to overflow-wrap: break-word. This is a correct CSS standards improvement (word-break: break-word is a deprecated alias). No action needed; noting for completeness.

Footer

Outcome: request-changes
This review applies to SHA 613f465a10ef7ea8758400ea13f393c2ffaee39b. Any push to the PR head clears this review and requires a new evaluation.

Previous run (8)

Review: #625

Head SHA: 1b9d8c4
Timestamp: 2026-05-03T00:00:00Z
Outcome: approve

Summary

This PR adds ESLint 9, Prettier, and Stylelint tooling for the admin SPA with sensible defaults — including XSS prevention (svelte/no-at-html-tags: error), component size enforcement, and TypeScript enforcement for Svelte script blocks. The source file changes are almost entirely Prettier reformatting with no behavioral changes. The one functional CSS fix (word-break: break-wordoverflow-wrap: break-word in Home.svelte) is correct — the former is a non-standard value. The ESLint configuration is well-structured with proper global ignores, TypeScript parser delegation for Svelte files, and a phased rollout approach (warn-level size rules now, error later). No security, correctness, or intent concerns.

Findings

Medium

(none)

Low

  • [style] web/admin/src/lib/auth/turnstile.ts:90 — The eslint-disable-next-line preserve-caught-error directive references a rule that does not appear in the ESLint configuration or any of the configured plugin presets (@eslint/js, typescript-eslint, eslint-plugin-svelte). This is likely a no-op unused disable directive. If the rule is intended for a future plugin, consider adding a TODO linking to the issue; otherwise, remove the directive or use the correct rule name.

Info

  • [style] PR description states "Phase 2: Add lint steps to CI workflow + pre-commit hooks" but this PR already adds a husky pre-commit hook (npx lint-staged). The phased rollout description may need updating to reflect that pre-commit hooks ship in Phase 1.
  • [correctness] The CSS change from word-break: break-word to overflow-wrap: break-word in Home.svelte is a correct Stylelint-driven fix — break-word is not a valid value for word-break per the CSS spec.

Footer

Outcome: approve
This review applies to SHA 1b9d8c4b47fccdbbefa20ea572785f9fb717eba5. Any push to the PR head clears this review and requires a new evaluation.

Previous run (9)

Review: #625

Head SHA: 616fcb2
Timestamp: 2026-05-03T00:00:00Z
Outcome: approve

Summary

This PR cleanly adds ESLint 9, Prettier, and Stylelint infrastructure to the admin SPA with well-chosen rules and a sensible phased rollout plan. All source file changes are purely Prettier reformatting with no logic modifications — verified by diff inspection. The one substantive CSS change (word-break: break-word to overflow-wrap: break-word) is a standards-compliance improvement. Security-relevant ESLint rules (no-at-html-tags, block-lang, require-each-key) are appropriately set to error, while size/style rules are set to warn for the initial phase. All new dependencies are devDependencies and do not affect production bundles.

Findings

Info

  • [style/conventions] web/admin/src/lib/auth/turnstile.ts:90 — The eslint-disable-next-line preserve-caught-error references a rule that is not visibly configured in eslint.config.js. If no plugin provides this rule, ESLint may report an unused directive warning (depending on reportUnusedDisableDirectives setting). Verify the rule exists or remove the disable comment.

Footer

Outcome: approve
This review applies to SHA 616fcb2806e90fd9fbbd38ea5a76d3a109791982. Any push to the PR head clears this review and requires a new evaluation.

Previous run (10)

Review: #625

Head SHA: 9d2fbb0
Timestamp: 2026-05-03T00:00:00Z
Outcome: comment-only

Summary

This PR adds ESLint 9 (flat config), Prettier, and Stylelint to the admin SPA, along with Prettier-reformatted source files. The changes are well-structured: linting config is sensible (XSS prevention via no-at-html-tags, dead-code detection, component size limits at warn level), and the code modifications are almost entirely mechanical formatting with one minor CSS improvement. Two low-severity observations are noted below but neither blocks merge.

Findings

Medium / Low / Info

  • [correctness] web/admin/src/lib/auth/turnstile.ts:~90 — The eslint-disable-next-line preserve-caught-error comment references a rule name (preserve-caught-error) that does not exist in ESLint core, typescript-eslint, or eslint-plugin-svelte. If reportUnusedDisableDirectives is enabled (or becomes the default in a future ESLint version), this will produce a warning. Consider removing the directive or replacing it with the correct rule name if this was intended to suppress a custom rule not yet configured.
    Severity: low
    Remediation: Verify the intended rule name or remove the disable comment and address the caught-error pattern directly.

  • [style] web/admin/src/routes/Home.svelte — The change from word-break: break-word to overflow-wrap: break-word is a good fix — word-break: break-word is non-standard. Just noting this is a behavioral change (not purely formatting), which is fine.
    Severity: info

Footer

Outcome: comment-only
This review applies to SHA 9d2fbb0dd6fc8746e29447d8f1c95892e303d7ec. Any push to the PR head clears this review and requires a new evaluation.

@fullsend-ai-review fullsend-ai-review Bot 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.

See the review comment for full details.

@fullsend-ai-review fullsend-ai-review Bot 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.

See the review comment for full details.

@fullsend-ai-review fullsend-ai-review Bot 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.

See the review comment for full details.

@fullsend-ai-review fullsend-ai-review Bot 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.

See the review comment for full details.

@ifireball ifireball left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd rather do this after we get #617 merged, to limit the amount of code I have to rebase (it would be huge anyway...)

@waynesun09 waynesun09 force-pushed the add-frontend-linting branch from 613f465 to 5661747 Compare May 6, 2026 00:56

@fullsend-ai-review fullsend-ai-review Bot 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.

See the review comment for full details.

@fullsend-ai-review fullsend-ai-review Bot 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.

See the review comment for full details.

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had any activity in the last month. It will be closed in 2 weeks if no further activity occurs. Remove the stale label to reset the inactivity timer.

@github-actions github-actions Bot added the stale label Jun 5, 2026
@waynesun09 waynesun09 force-pushed the add-frontend-linting branch from 9b15e3b to 4d4709e Compare June 15, 2026 16:01
@waynesun09 waynesun09 changed the title feat(admin): add ESLint, Prettier, and Stylelint for admin SPA feat: add ESLint, Prettier, and Stylelint for web apps Jun 15, 2026
@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

Site preview

Preview: https://e770992a-site.fullsend-ai.workers.dev

Commit: 758ce3e4ac1de101d0ae2b166a9d67ebd8d903c4

@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 15, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:05 PM UTC · Completed 4:20 PM UTC
Commit: 4d4709e · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot 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.

See the review comment for full details.

Comment thread eslint.config.js Outdated
import ts from "typescript-eslint";
import svelte from "eslint-plugin-svelte";
import globals from "globals";
import svelteConfig from "./web/admin/svelte.config.js";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] config-reference-inconsistency

eslint.config.js imports only ./web/admin/svelte.config.js but the lint script targets both web/admin/src/ and web/docs/src/. The svelteConfig is applied to all **/*.svelte files, meaning web/docs Svelte files are parsed using admins config. If the configs ever diverge, linting results for web/docs may be incorrect.

Suggested fix: Either document why admins config can be shared, or use separate ESLint config blocks for web/admin/src and web/docs/src with their respective svelte.config.js files.

Comment thread web/docs/src/App.svelte Outdated
const legacy = legacyPathnameDocRest();
if (legacy !== null) {
const u = new URL(window.location.href);
const u = new SvelteURL(window.location.href);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] unnecessary reactivity

SvelteURL (a reactive URL wrapper) is used in syncRouteFromLocation, a purely imperative function. Standard new URL() would be functionally identical and avoids importing the reactive wrapper unnecessarily.

@@ -96,6 +87,7 @@ export async function obtainTurnstileToken(
return await obtainTurnstileTokenWithSignal(siteKey, merged);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[info] nonexistent eslint rule

The eslint-disable-next-line preserve-caught-error comment references a rule that does not exist in the ESLint configuration. ESLint will report an unused disable directive warning.

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 15, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:34 PM UTC · Completed 4:49 PM UTC
Commit: a3f68d6 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot 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.

See the review comment for full details.

@@ -96,6 +87,7 @@ export async function obtainTurnstileToken(
return await obtainTurnstileTokenWithSignal(siteKey, merged);
} catch (e) {
if (deadline.signal.aborted && !userSignal?.aborted) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] dead-code

The ESLint disable comment // eslint-disable-next-line preserve-caught-error references a rule name that does not exist in the ESLint configuration (not from @eslint/js, typescript-eslint, or eslint-plugin-svelte). The comment is a no-op — ESLint silently ignores disable comments for unknown rules.

Suggested fix: Remove the dead comment or replace with the correct rule name. Consider adding { cause: e } to the thrown Error directly, matching the pattern used in orgConfigParse.ts and githubClient.ts in this same PR.

@@ -624,15 +591,15 @@
Access an organisation owner may need to approve:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] ineffective-each-key

The {#each} keys added for missingInstallRequirements and helpBullets use array index (i) as the key. Index-based keys are functionally equivalent to having no key for Svelte DOM diffing. Same pattern in web/docs/src/lib/DocTreeNav.svelte lines 109 and 141. These satisfy svelte/require-each-key but are no-ops.

@waynesun09

Copy link
Copy Markdown
Member Author

@ifireball This has been rebased onto latest main and updated based on review feedback. The primary focus has shifted to web/docs/src — the docs viewer is where active development is happening. web/admin/src is included as scaffolding since admin work is on hold, but the linting infrastructure covers both.

Key changes since the last review:

  • Husky removedlint-staged is now a local hook in .pre-commit-config.yaml, no conflict with the existing pre-commit framework
  • Per-app Svelte configs in ESLint (adminSvelteConfig / docsSvelteConfig) so each app can diverge independently
  • prettier-plugin-svelte pinned to ~3.5.1 to avoid breakage on v4 upgrade
  • ESLint errors in web/docs fixed ({#each} keys, {@html} suppression, CSS selector ordering)

Ready for review when you have a chance.

@github-actions github-actions Bot removed the stale label Jun 16, 2026
@rh-hemartin rh-hemartin requested a review from ifireball June 19, 2026 09:29
Add frontend linting scaffolding for the admin SPA (Svelte 5 + Vite 6 + TypeScript):
- ESLint flat config with typescript-eslint, eslint-plugin-svelte, and Prettier compat
- Prettier with prettier-plugin-svelte for consistent code formatting
- Stylelint with stylelint-config-standard and stylelint-config-html/svelte
- npm scripts: lint, lint:fix, format, format:check, stylelint, stylelint:fix

Signed-off-by: Wayne Sun <gsun@redhat.com>
@waynesun09 waynesun09 added this pull request to the merge queue Jun 23, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jun 23, 2026
@waynesun09 waynesun09 force-pushed the add-frontend-linting branch from 9faa736 to af9b20b Compare June 23, 2026 14:42
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 23, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 2:45 PM UTC · Ended 2:48 PM UTC
Commit: 4e21a60 · View workflow run →

Signed-off-by: Wayne Sun <gsun@redhat.com>
- Move ignores block to first position in ESLint flat config array
  per ESLint 9 convention (prevents accidental non-global ignore if
  a files key is later added to the same object)
- Spread ts.configs.recommended (it's an array of 3 config objects)
  instead of relying on defineConfig to flatten nested arrays
- Remove no-op color-no-hex: null from stylelint config (rule does
  not exist in stylelint-config-standard)

Signed-off-by: Wayne Sun <gsun@redhat.com>
Run ESLint, Prettier, and Stylelint on staged files before each
commit. Follows the same pattern as openkaiden/kaiden: Husky v9
with lint-staged, scoped to web/admin/src files only.

Signed-off-by: Wayne Sun <gsun@redhat.com>
- Attach cause to re-thrown errors in githubClient and orgConfigParse
- Prefix unused appSlug and scanComplete vars with underscore
- Add each-block keys in OrgList popover lists
- Suppress require-yield in test generators that intentionally throw
- Replace deprecated clip with clip-path in sr-only
- Replace deprecated word-break: break-word with overflow-wrap

Signed-off-by: Wayne Sun <gsun@redhat.com>
The variable was written in 7 locations but never read, creating
unnecessary reactive tracking overhead in Svelte 5.

Signed-off-by: Wayne Sun <gsun@redhat.com>
- Remove unused toml-eslint-parser devDependency
- Scope browser globals to web/{admin,docs}/src/** so test files
  and non-browser contexts don't silently inherit browser APIs

Assisted-by: Claude (review, fix)
Signed-off-by: Wayne Sun <gsun@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 23, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:52 PM UTC · Completed 3:04 PM UTC
Commit: 758ce3e · View workflow run →

@waynesun09 waynesun09 added this pull request to the merge queue Jun 23, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jun 23, 2026

@fullsend-ai-review fullsend-ai-review Bot 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.

See the review comment for full details.

return await obtainTurnstileTokenWithSignal(siteKey, merged);
} catch (e) {
if (deadline.signal.aborted && !userSignal?.aborted) {
// eslint-disable-next-line preserve-caught-error -- TODO: attach cause once callers handle it

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] dead-code

The ESLint disable comment references a rule (preserve-caught-error) that does not exist in the ESLint configuration. The disable directive is a no-op.

@fullsend-ai-review fullsend-ai-review Bot added the type/chore Maintenance and housekeeping tasks label Jun 23, 2026
@waynesun09 waynesun09 added this pull request to the merge queue Jun 23, 2026
Merged via the queue into main with commit 170c1f0 Jun 23, 2026
18 checks passed
@waynesun09 waynesun09 deleted the add-frontend-linting branch June 23, 2026 15:22
@fullsend-ai-retro

fullsend-ai-retro Bot commented Jun 23, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 3:27 PM UTC · Completed 3:36 PM UTC
Commit: 758ce3e · View workflow run →

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #625 — Add ESLint, Prettier, and Stylelint for web apps

Timeline: PR opened May 3 by waynesun09, merged June 23 (~51 days). 10 review bot runs, 2 failed reviews (June 22), 1 unrelated fix run failure. Went stale mid-cycle. Merged with human approval overriding bot's final CHANGES_REQUESTED.

What went well:

  • The review bot correctly identified a high-severity true positive: removal of toml-eslint-parser breaks cloudflare_site/scripts/patch-wrangler-rate-limit-namespace-ids.mjs, which imports parseTOML from that package. This was flagged consistently in runs 7–9.
  • The bot also correctly caught the Husky/pre-commit conflict in run 4, which the author later resolved.

What went poorly:

  • Two review runs failed on June 22 with GitHub API 422 errors when submitting inline comments referencing positions outside the PR diff. This is tracked as #1067, and #1349 claims it was fixed by PR fix: validate inline review comment lines against diff hunks #1348 — but the failures recurred, indicating the fix is incomplete.
  • The bot gave inconsistent verdicts: CHANGES_REQUESTED on commit 4 (Husky conflict), then APPROVED on commit 5 with the same issue still present. Already tracked by #947 and #1389.
  • 10 review runs is excessive for this PR. Covered by #2111, #1370, and #1013.
  • Protected-path finding kept re-firing on a human-authored PR. Covered by #1551 and #1583.

Proposals: 1 new proposal below. Most other improvement areas are already tracked by existing open issues (referenced above).

Proposals filed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/chore Maintenance and housekeeping tasks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants