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
109 changes: 94 additions & 15 deletions .agents/skills/generate-app-benchmark/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ Copy this checklist and track progress:
- [ ] Step 1: Resolve the best practice's ordered acceptance test slugs
- [ ] Step 2: Resolve datetime (use input or current UTC time)
- [ ] For each app:
- [ ] Step 3: Locate manual.json + proof images
- [ ] Step 3: Locate manual.json + proof media
- [ ] Step 3b: Normalize proof media to .png / .gif
- [ ] Step 4: Generate the best-practice index.tsx
- [ ] Step 5: Wire it into the app's main benchmarks/index.tsx
- [ ] Step 6: Typecheck
```

### Step 1: Resolve acceptance test slugs (once)
### Step 1: Resolve acceptance test slugs + canonical name/address spans (once)

The `manual.json` test `id`s map **positionally** (1-based) to the best
practice's acceptance tests, in declaration order. Read them from the best
Expand All @@ -47,10 +48,30 @@ practice's technical details:
ensawards.org/data/ens-best-practices/**/<best-practice-slug>/technicalDetails.tsx
```

Grep `acceptanceTestSlug` there; the order top-to-bottom is the `id` order
(`id: 1` → first slug, etc.).
Sort the acceptance tests by `order` (slug as the tiebreaker) before mapping `manual.json` ids to slugs.

### Step 3: Locate manual.json + proof images (per app)
While reading that file, also note the **canonical ENS name and expected
address** each acceptance test exercises. These are defined as exported `*Span`
constants (e.g. `vitalikEnsNameSpan`, `vitalikAddressSpan`,
`lightkeeperEnsNameSpan`, `lightkeeperAddressSpan`, ...) that render the name /
address in the correct code style. The benchmark notes **must** reference these
exported spans rather than vague phrasing — import them from the best practice's
`technicalDetails` module:

```tsx
import {
vitalikEnsNameSpan,
vitalikAddressSpan,
// ...one pair (or more) per test you emit
} from "data/ens-best-practices/<category>/<best-practice-slug>/technicalDetails";
```

If a span you need isn't exported yet, add `export` to its `const` declaration
in `technicalDetails.tsx` (a non-breaking change). Map each acceptance test id to
its spans (name + expected chain + expected address) so Step 4 can write concrete
notes.

### Step 3: Locate manual.json + proof media (per app)

Search the app's benchmarks dir:

Expand All @@ -60,9 +81,52 @@ ensawards.org/data/apps/<app>/benchmarks/**/manual.json

Pick the `manual.json` whose directory corresponds to the target best practice
(directory name may differ slightly from the slug, e.g. `deposit-address` vs
`deposit-addresses`). Proof images sit in the **same directory**, named with a
trailing test id: `at-<id>.<ext>` or `ac-<id>.<ext>` (`.png`/`.gif`/`.jpg`).
Match each image to a test by its id. A test may have no image.
`deposit-addresses`). Proof media sit in the **same directory**, named with a
trailing test id: `at-<id>.<ext>` or `ac-<id>.<ext>`. Match each file to a test
by its id. A test may have no media. Source files can be images (`.png`, `.jpg`,
`.webp`, ...) or screen recordings (`.mov`, `.mp4`, ...).

### Step 3b: Normalize proof media to .png / .gif

The benchmark file may only reference `.png` or `.gif` assets. Normalize each
proof file `at-<id>.<ext>` in place, then `git rm` the original if its extension
changed:

| Source | Action |
| ----------------------------------------------------------- | ----------------------- |
| `.png` | keep as-is |
| `.gif` | keep as-is |
| other image (`.jpg`/`.jpeg`/`.webp`/`.heic`/`.tiff`/`.bmp`) | convert → `at-<id>.png` |
| video (`.mov`/`.mp4`/`.m4v`/`.webm`/`.avi`/`.mkv`) | convert → `at-<id>.gif` |

Conversions can be finicky; pick commands by OS (default to **macOS**).

**Image → PNG**

macOS (built-in `sips`, no install):

```bash
sips -s format png "at-1.jpg" --out "at-1.png" && git rm "at-1.jpg"
```

Windows (ImageMagick):

```powershell
magick "at-1.jpg" "at-1.png"; git rm "at-1.jpg"
```

**Video → GIF** (both OSes use `ffmpeg`; install via `brew install ffmpeg` /
`winget install Gyan.FFmpeg` if missing):

```bash
ffmpeg -y -i "at-5.mov" \
-vf "fps=12,scale=800:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \
-loop 0 "at-5.gif" && git rm "at-5.mov"
```

Tune `fps` / `scale` down if the resulting `.gif` is large. After this step every
proof file is a `.png` or `.gif`, and that final extension is what the import in
Step 4 must reference.

`manual.json` schema:

Expand Down Expand Up @@ -95,8 +159,15 @@ test present in `manual.json`** (keyed by the slug at that id's position).

Notes prose: write a short natural sentence per test combining the relevant
`method` (per-test override if present, else the global `method`) and the
`reason`. For failures, emphasize the negative outcome with `<i>NOT</i>` as in
the examples. Include an `<img>` only when a matching proof image exists.
`reason`. **Always name the concrete ENS name tested and the expected address**
using the spans imported in Step 1 (e.g. "The resolved Ethereum Mainnet address
of {vitalikEnsNameSpan} is correct ({vitalikAddressSpan}).") instead of vague
phrasing like "The resolved address is correct.". For passes, state the resolved
name maps to the expected address; for failures, name the tested ENS name and the
expected address, then emphasize the negative outcome with `<i>NOT</i>` (e.g. the
shown value was wrong / an error appeared). Include an `<img>` only when a matching
proof image exists. `Not Applicable` notes generally don't reference a specific
address (see Special overrides).

The `reason` and `method` strings are **not strict** — they are author notes,
not literal copy. Freely rephrase, correct, and expand them so the prose reads
Expand All @@ -114,6 +185,12 @@ import { acceptanceTestDetailsContainerStyles } from "data/ens-best-practices/st

import { parseTimestamp } from "@ensnode/ensnode-sdk";

import {
<nameSpanForId1>,
<addressSpanForId1>,
// ...more spans, one (or more) per emitted test
} from "data/ens-best-practices/<category>/<best-practice-slug>/technicalDetails";

import { cn } from "@/utils/tailwindClassConcatenation";

import at1Proof from "./at-1.png";
Expand All @@ -125,7 +202,7 @@ const <camelCaseBestPracticeSlug> = {
contributions: [{ from: contributors.<contributor>, lastUpdated: parseTimestamp("<datetime>") }],
notes: (
<div className={cn(acceptanceTestDetailsContainerStyles, "w-full")}>
<p className="w-full"><sentence from method + reason></p>
<p className="w-full"><sentence from method + reason, naming {<nameSpan>} and {<addressSpan>}></p>
<img
alt="<app> correctly resolves ..."
src={at1Proof.src}
Expand All @@ -151,7 +228,8 @@ Result-specific entry examples — match these renderings:
notes: (
<div className={cn(acceptanceTestDetailsContainerStyles, "w-full")}>
<p className="w-full">
Tested using the search flow on etherscan.io. The resolved address is correct.
Tested using the search flow on etherscan.io. The resolved Ethereum Mainnet address of{" "}
{vitalikEnsNameSpan} is correct ({vitalikAddressSpan}).
</p>
<img
alt="Etherscan correctly resolves the deposit address"
Expand All @@ -163,7 +241,7 @@ Result-specific entry examples — match these renderings:
} as const satisfies AcceptanceTestBenchmark,
```

**Failed** (emphasize the failure):
**Failed** (name the ENS name + expected address, then emphasize the failure):

```tsx
"correctly-resolve-names-for-different-evm-chains": {
Expand All @@ -172,8 +250,9 @@ Result-specific entry examples — match these renderings:
notes: (
<div className={cn(acceptanceTestDetailsContainerStyles, "w-full")}>
<p className="w-full">
Tested using the search flow on basescan.org. The shown address was the mainnet
address, <i>NOT</i> the Base address of that name.
Tested using the search flow on basescan.org. For {lightkeeperEnsNameSpan} the shown
address was the mainnet address, <i>NOT</i> the expected Base chain address
({lightkeeperAddressSpan}).
</p>
<img
alt="Etherscan fails to resolve the Base deposit address"
Expand Down
166 changes: 166 additions & 0 deletions .agents/skills/rearrange-app-benchmarks/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
---
name: rearrange-app-benchmarks
description: >-
Migrate an ENS Awards app's benchmarks/index.tsx from the old single-file
layout to the new per-best-practice split-module layout, without changing any
benchmark data. Use when asked to "rearrange <app> to new structure" / port an
app's benchmarks to the new structure. Works for any app under
ensawards.org/data/apps/.
disable-model-invocation: true
---

# Rearrange App Benchmarks

Restructure an app's benchmarks to the new layout (canonical example:
`etherscan-explorer`) from the old layout (e.g. `blockscout-explorer`). This is a
**pure refactor**: the app must compile, lint, and render exactly as before. Do
**not** add, remove, or change any benchmark data, results, contributions, notes,
or images.

## Input

- **app** — one app slug (directory under `ensawards.org/data/apps/`).

## Target layout (new)

`benchmarks/index.tsx` references split modules and orders best practices as:

1. `"ensv2-ready-resolution"` — imported from `./resolution/ensv2-ready-resolution`
2. `"deposit-addresses"` — imported from `./resolution/deposit-address`
3. legacy contract-naming best practices (`display-named-smart-contracts-mainnet`,
`display-named-smart-contracts-l2-chains`) — left **inline and unchanged**, moved
to the **bottom**, under their existing `TODO: Contract Naming ...` comment.

Each split module lives at `benchmarks/resolution/<dir>/index.tsx` and
`export default`s an `AcceptanceTestBenchmarks` object. Its proof image(s) sit in
the same directory.

## Workflow

```
- [ ] Step 1: Read the app's benchmarks/index.tsx; note which best practices exist
- [ ] Step 2: Extract ensv2-ready-resolution into a split module (if present)
- [ ] Step 3: Extract deposit-addresses into a split module (if present)
- [ ] Step 4: Rewrite the main index.tsx (reorder + imports)
- [ ] Step 5: Typecheck + lint
```

### Step 2: Extract `ensv2-ready-resolution` (only if the key exists)

1. Create `benchmarks/resolution/ensv2-ready-resolution/`.
2. Move the proof image(s) imported by this block into that directory, renamed `ac-1.<original-ext>`, `ac-2.<original-ext>`, ... using `git mv` to preserve history.
3. Create `benchmarks/resolution/ensv2-ready-resolution/index.tsx` that
`export default`s the **same** benchmark object, copied **verbatim** (results,
contributions, notes, alt text unchanged). Update the proof image import(s) to
the local `./ac-1.png`.
Comment thread
sevenzing marked this conversation as resolved.

Module template (mirror the verbatim content; only imports + image paths change):

```tsx
import type { AcceptanceTestBenchmark } from "data/acceptance-tests/types";
import type { AcceptanceTestBenchmarks } from "data/benchmarks/types";
import { BenchmarkResults } from "data/benchmarks/types";
import contributors from "data/contributors";
import { acceptanceTestDetailsContainerStyles } from "data/ens-best-practices/styles";

import { parseTimestamp } from "@ensnode/ensnode-sdk";

import { cn } from "@/utils/tailwindClassConcatenation";

import correctlyResolveEnsv2TestNameAddressProofImage from "./ac-1.png";

const ensv2ReadyResolution = {
// ...the exact same entry/entries that were inline in the main file...
} as const satisfies AcceptanceTestBenchmarks;

export default ensv2ReadyResolution;
```

Only import the symbols the moved content actually uses (e.g. if every entry is
`undefined`, the module needs just `AcceptanceTestBenchmarks`).

### Step 3: Extract `deposit-addresses` (only if the key exists)

1. Create `benchmarks/resolution/deposit-address/` if it doesn't exist.
2. If a legacy `benchmarks/deposit-address/` source folder exists (e.g.
`manual.json` + `at-*.png`), `git mv` it to `benchmarks/resolution/deposit-address/`
so source assets are co-located for a later `generate-app-benchmark` run.
**Do not** consume `manual.json` here — this skill adds no benchmarks.
3. Create `benchmarks/resolution/deposit-address/index.tsx` that `export default`s
the **same** `deposit-addresses` record copied verbatim from the main file
(typically every acceptance test mapped to `undefined`).

All-`undefined` module example:

```tsx
import type { AcceptanceTestBenchmarks } from "data/benchmarks/types";

const depositAddresses = {
"correctly-resolve-direct-onchain-subname-address": undefined,
"correctly-resolve-names-requiring-normalization": undefined,
"correctly-implement-ccip-read-for-eth-subnames": undefined,
"correctly-implement-ccip-read-for-offchain-dns-names": undefined,
"correctly-resolve-names-for-different-evm-chains": undefined,
"correctly-resolve-names-for-bitcoin": undefined,
"correctly-resolve-names-for-solana": undefined,
"correctly-handle-resolution-for-chains-with-invalid-address-formatting": undefined,
} as const satisfies AcceptanceTestBenchmarks;

export default depositAddresses;
```

### Step 4: Rewrite the main `benchmarks/index.tsx`

- Add imports for the new modules (`./resolution/ensv2-ready-resolution`,
`./resolution/deposit-address`).
- Remove the proof-image import(s) that moved into the ensv2 module.
- Keep imports still used by the inline legacy blocks (`AcceptanceTestBenchmark`,
`BenchmarkResults`, `contributors`, `acceptanceTestDetailsContainerStyles`,
`parseTimestamp`, `cn`, the example proof image).
- Reorder the `benchmarks` object: `ensv2-ready-resolution` first,
`deposit-addresses` second, then the legacy contract-naming blocks (verbatim) at
the bottom under their `TODO: Contract Naming ...` comment. Preserve any leading
commented-out blocks (e.g. `recognize-all-ens-names`).

Resulting shape:

```tsx
import depositAddresses from "./resolution/deposit-address";
import ensv2ReadyResolution from "./resolution/ensv2-ready-resolution";
// ...kept imports...

const benchmarks = {
// ...preserved commented-out blocks...
"ensv2-ready-resolution": ensv2ReadyResolution,
"deposit-addresses": depositAddresses,

// TODO: `Contract Naming` category is temporarily hidden ...
"display-named-smart-contracts-mainnet": { /* unchanged inline */ },
"display-named-smart-contracts-l2-chains": { /* unchanged inline */ },
} as const satisfies BestPracticeBenchmarks;

defineAppBenchmarks(<App>, benchmarks);

export default benchmarks;
```

Only include the keys that existed before. Never introduce a best practice the
app didn't already have.

### Step 5: Verify

```bash
cd ensawards.org && pnpm astro sync && pnpm exec tsc --noEmit
```

Then lint the touched files. Everything must pass as before. The IDE may show a
stale "is not a module" error for a just-deleted/renamed file; `tsc` is the
source of truth.

## Notes

- Pure refactor: identical compiled behavior. If you find yourself writing new
result/notes content, stop — that belongs to `generate-app-benchmark`.
- JSX requires `.tsx`. A split module with only `undefined` entries has no JSX, but
still use `.tsx` to match the target layout and ease later population.
- Use `git mv` for image/folder moves to preserve history.
Loading
Loading