fix: Add architectural lint rules with agent-friendly error messages (#232)

- landing/eslint.config.js: ban imports from web-app paths (rule 1),
  direct RPC clients from viem/@wagmi/vue (rule 2), and axios (rule 4)
- web-app/eslint.config.js: ban string interpolation inside GraphQL
  query/mutation property values (rule 3); fixes 4 pre-existing violations
  in usePositionDashboard, usePositions, useSnatchNotifications,
  useWalletDashboard by migrating to variables: {} pattern
- services/ponder/eslint.config.js: ban findMany() calls that lack a
  limit parameter to prevent unbounded indexed-data growth (rule 5)

All error messages follow the [what is wrong][rule][how to fix][where to
read more] template so agents and humans fix on the first try.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
openhands 2026-02-24 20:44:17 +00:00
parent e3d45bb8ef
commit 0d761744df
7 changed files with 95 additions and 13 deletions

View file

@ -107,8 +107,8 @@ export function usePositionDashboard(positionId: Ref<string>) {
const res = await axios.post(
endpoint,
{
query: `query PositionDashboard {
positions(id: "${id}") {
query: `query PositionDashboard($id: String!) {
positions(id: $id) {
id
owner
share
@ -142,6 +142,7 @@ export function usePositionDashboard(positionId: Ref<string>) {
}
}
}`,
variables: { id },
},
{ timeout: GRAPHQL_TIMEOUT_MS }
);

View file

@ -194,8 +194,8 @@ export async function loadMyClosedPositions(chainId: number, endpointOverride: s
const res = await axios.post(
targetEndpoint,
{
query: `query ClosedPositions {
positionss(where: { status: "Closed", owner: "${account.address?.toLowerCase()}" }, limit: 1000) {
query: `query ClosedPositions($owner: String!) {
positionss(where: { status: "Closed", owner: $owner }, limit: 1000) {
items {
id
lastTaxTime
@ -214,6 +214,7 @@ export async function loadMyClosedPositions(chainId: number, endpointOverride: s
}
}
}`,
variables: { owner: account.address?.toLowerCase() ?? '' },
},
{ timeout: GRAPHQL_TIMEOUT_MS }
);

View file

@ -40,16 +40,16 @@ function setLastSeen(address: string, timestamp: string): void {
async function fetchSnatchEvents(endpoint: string, address: string, since: string | null): Promise<SnatchEvent[]> {
const holder = address.toLowerCase();
const whereClause =
since !== null
? `{ holder: "${holder}", type: "snatch_out", timestamp_gt: "${since}" }`
: `{ holder: "${holder}", type: "snatch_out" }`;
const where: { holder: string; type: string; timestamp_gt?: string } = { holder, type: 'snatch_out' };
if (since !== null) {
where.timestamp_gt = since;
}
const res = await axios.post(
endpoint,
{
query: `query SnatchEvents {
query: `query SnatchEvents($where: transactionsFilter!) {
transactionss(
where: ${whereClause}
where: $where
orderBy: "timestamp"
orderDirection: "desc"
limit: 20
@ -57,6 +57,7 @@ async function fetchSnatchEvents(endpoint: string, address: string, since: strin
items { id timestamp tokenAmount ethAmount txHash }
}
}`,
variables: { where },
},
{ timeout: GRAPHQL_TIMEOUT_MS }
);

View file

@ -67,8 +67,8 @@ export function useWalletDashboard(address: Ref<string>) {
const res = await axios.post(
endpoint,
{
query: `query WalletDashboard {
holders(address: "${addr}") {
query: `query WalletDashboard($addr: String!) {
holders(address: $addr) {
address
balance
totalEthSpent
@ -88,7 +88,7 @@ export function useWalletDashboard(address: Ref<string>) {
holderCount
}
}
positionss(where: { owner: "${addr}" }, limit: 1000) {
positionss(where: { owner: $addr }, limit: 1000) {
items {
id
share
@ -106,6 +106,7 @@ export function useWalletDashboard(address: Ref<string>) {
}
}
}`,
variables: { addr },
},
{ timeout: GRAPHQL_TIMEOUT_MS }
);