Repository navigation
Conversation
…3660) * Unify text truncation behind a single `Truncate` primitive Replace the four scattered truncation components (HashStringShorten, HashStringShortenDynamic, TruncatedText, TruncatedTextTooltip) with one public toolkit primitive, `Truncate`, that dispatches on a `type` prop to three internal technique modules: - `middle` (default) — measured middle-ellipsis (container width, binary search) - `middle-static` — fixed char count, pure string slice, SSR-safe, cheapest - `end` — measured end-ellipsis (CSS text-overflow) App code now picks *what* (`value`) and *how* (`type`); implementation details stay internal. `TruncatedTextTooltip` becomes the toolkit-internal `OverflowTooltip` primitive (still used directly by tag/badge, which wrap arbitrary children). `shorten-string` moves into the truncation dir. The entity hub keeps its `truncation` prop and maps to `Truncate` in one place, so the 115 entity call-sites are untouched. Its tooltip config is memoized to preserve the leaf `React.memo` in table rows. Tooltip contract is now uniform: shown only when the value is truncated; `tooltip.content` overrides content (not the trigger); `tooltip={false}` suppresses it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Address review on the Truncate primitive - Force the tooltip when it carries actionable UI: add `always` to the tooltip config, honored by all three techniques, and set it wherever `tooltipContentAfter` is present so the advanced-filter link stays reachable on wide viewports (F1). - Detect truncation by content, not length, in TruncateMiddle (F2). - Name-export the three technique modules per the toolkit convention (F3). - Move `shorten-string` to `toolkit/utils/texts`, name-export it, and re-export from the package index; update all importers (F4). - Fix the stale fontWeight-observer comment and drop a reviewer-facing comment (F6, F7). Also align OverflowTooltip's props with Chakra's Tooltip (content/positioning). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Fix end-truncation layout and tooltip regressions `Truncate type="end"` (TruncateEnd) was the only technique module missing `asChild` on its Skeleton, so the non-loading fast path wrapped the text in an extra `div`. Two visual regressions followed: - TokenEntity: OverflowTooltip measured the wrapper div instead of the clipping span, so a truncated symbol reported not-truncated and its tooltip never opened. Add `asChild` so the text lives in the single clipping/measured span, matching TruncateMiddle/MiddleStatic. - TokenInventory: the ID# row (previously a block <Link>) now wraps the inline-block Truncate, adding the baseline-descender gap and growing the row ~5px. Render that Truncate as `display="block"`, as before. Verified in Docker against the CI baselines: both target tests plus a 53-test end/tail-truncation sweep pass, no screenshots updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * pass all tooltip props into underlaying components * fix toolkit build * fix tests * [skip ci] pw setup fixes --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Add a Slack reply watcher to the product-task workflow New slack-watch skill holds a Socket Mode socket and prints one line per new reply in a watched thread; grill-the-task Step 3 launches it and drives each question to settled; questions gain a Resolved-when acceptance criterion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Cover channel threads too, via a bot token Add a third Keychain token (bot). Route catch-up reads by channel prefix — user token for DMs, bot token for channels/groups it was invited to — and ignore both our identities (developer and Honk) when deciding what is a new reply. One Socket Mode connection carries the app's bot and user events. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Resolve review findings on the Slack watcher (F1–F4) F1: name both runtimes in slack-watch — Claude Code (Monitor) and Cursor (background Shell + notify_on_output), mapping launch/stop/escalate to each. F2: qualify grill-the-task's "never send unreviewed outreach" absolute to "new" outreach, exempting the already-approved in-thread follow-ups. F3: state the `Resolved when:` criterion is session-held before to-spec, and split a sufficient answer's landing spot by task progress (questions.md once to-spec has run, else held in session). F4: retry the whole slack-watch init on a thrown startup auth.test, so the socket never opens with an empty ownIds and reports our own posts as NEW. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…lds (blockscout#3664) * Add spec for tx details "to" & "value" UI/UX changes (blockscout#3661) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Restyle token-transfer "View all" link on tx details Remove the leading navigation/tokens icon before the "View all" link in the token-transfer detail rows and apply the secondary link style. The overflow show-condition and Token-transfers-tab target are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Surface batch recipients in tx details "To" and "Value" For an Eden sponsored (batch) transaction with more than one unique recipient, the "To" field now lists the distinct recipients (first row rich, rows 2+ bare address, capped at 5 with a "View all (N)" link) and the "Value" field appends a "to N recipients" link. Recipients are de-duplicated by address; both links expand the Details section. Extract the "To" field into TxDetailsTo, derive the breakdown in a shared getBatchRecipients helper, and add an endContent slot to DetailedInfoNativeCoinValue for the "Value" link. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Prune task working files for tx details to/value task Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Fix batch-recipient counting and rendering in tx details "To" Exclude contract-creation calls (null `to`) from the unique-recipient count so "N recipients" / "View all (N)" reflect addresses only (FR-6), and render the recipient list by matching the top-level `to` by hash instead of assuming it is the first call — keeping the shown rows a subset of the counted recipients in every case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Update styles for "In/Out/Self" badges Resolves blockscout#3652 * update screenshots
* Main page stats failover improvements Resolves blockscout#3655 * review fixes * correct the data guard and make widgets data source independent from each other * more review fixes
* Add spec for cyclomatic-complexity and CRAP-score CI gate (blockscout#3663) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Break blockscout#3663 CI-gate spec into tickets Add the four-ticket decomposition for the complexity/CRAP CI gate: complexity CLI (tracer bullet), CRAP + coverage/JSX, CI wiring, and threshold calibration. Amend the spec to drop the third-party service from the shipped tool and add focused mode + in-tool threshold config. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Add complexity CLI with diff-scoped cyclomatic gate Introduce tools/code-complexity/: a CLI that computes per-function cyclomatic complexity by walking the TS/TSX AST (ts.createSourceFile, no type-checker), matching ESLint's complexity rule. It gates the raw complexity of functions a diff touches against a configurable cap, prints a table of all checked functions sorted by complexity, and exits non-zero on any violation. A focused mode scores every function in explicit file paths, bypassing diff-scoping. Wired to `pnpm test:code-complexity` via a compile-on-run wrapper (dist/ is git-ignored). Coverage, CRAP, and JSX handling arrive in later tickets. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Add coverage-aware CRAP gate with self-generating coverage Extend tools/code-complexity with the second, coverage-aware gate and make coverage self-service so the tool is usable in one command locally and in CI. - CRAP: AST-based JSX detection, per-function line coverage joined from a v8/istanbul coverage-final.json, CRAP = c²·(1−cov)³+c, second configurable threshold. Report gains COV/CRAP/BROKE columns sorted by CRAP descending. - Coverage sourcing, two axes mirroring vitest. Selection: full-repo (default), focused (paths), diff (--changed). Source: generated by running vitest scoped to the selection (default), --coverage-file to consume a prebuilt report (the CI path, skips vitest), or --no-coverage. Vitest output hidden unless --verbose; a no-test or failing run degrades gracefully. - The CRAP half applies to JSX-less logic files and to JSX components with a co-located vitest spec; visual-only components stay complexity-only. *.primed.spec.tsx are excluded from coverage runs (page mounts, no behavior), and vitest is skipped entirely when nothing in the selection needs coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Wire the complexity/CRAP gate into CI with GitHub annotations The vitest_tests PR run now emits coverage-final.json, and a diff-scoped post-step gates on it; under $GITHUB_ACTIONS the tool prints ::error annotations for offenders and writes the report table to the job summary. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Calibrate complexity/CRAP thresholds and stop counting optional chaining Run the tool repo-wide against whole-suite coverage to produce the complexity and CRAP distributions, then set the caps from the far tail. Drop optional chaining `?.` from the cyclomatic count: TypeScript already proves each site's nullability so no test covers it, and it was the largest source of tail inflation, dominated by JSX components full of defensive prop reads. Recorded as ADR 0004; CONTEXT.md points to it rather than restating the rationale, and the durable docs carry no threshold numbers (which will be re-tuned) or references to disposable tickets. Also records ticket 03's human-confirmation checkboxes, left uncommitted after the CI-wiring commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Scope blockscout#3663 ticket 05: per-kind complexity caps + per-function CRAP Turn the brief into a spec: one per-function "directly contains JSX" classifier drives two complexity caps (jsx backstop / behavior) and moves CRAP applicability from file-level to per-function (behavior functions only). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Split blockscout#3663 complexity gate into per-kind caps + per-function CRAP Classify each function by whether JSX appears directly in its own body: `jsx` render bodies get a high complexity backstop and no CRAP; `behavior` functions (handlers, hooks, utils) get a tighter cap and the CRAP gate. This replaces the single complexity cap and the file-level CRAP-applicability rule with per-function routing. - complexity.ts: emit containsJsx per function (innermost-frame marking); export JSX_KINDS, reused by jsx.ts - config.ts: DEFAULT_MAX_COMPLEXITY_JSX 30 / _BEHAVIOR 12 / CRAP 80, calibrated from a fresh full-repo run (?. not counted, ADR 0004) - report.ts: FunctionKind, maxComplexityFor, Thresholds shape, KIND column - analyze.ts: per-function cap selection + CRAP on behavior functions only - index.ts: --max-complexity-jsx / --max-complexity-behavior flags (bare --max-complexity sets both); coverageApplies -> fileNeedsCoverage (generation scope only) - github.ts: kind-aware complexity cap in annotations - CONTEXT.md: per-function rule for both gates; notes.md records calibration Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Scope blockscout#3663 ticket 06: cognitive-complexity readability cap Replaces the stopgap cyclomatic caps with a Cognitive Complexity cap as the sole readability gate (per-kind, both classes); cyclomatic survives only as the CRAP input. Six agent leaves, one human cap sign-off. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Replace the blockscout#3663 complexity gate with a Cognitive Complexity readability cap Cyclomatic complexity was a poor "should a human decompose this?" signal — it scored flat switches and wide if-ladders the same as deeply-nested logic. Swap the readability gate to Cognitive Complexity (SonarSource model): flat control flow scores cheap, nesting is penalised progressively, boolean runs collapse. Cyclomatic complexity stays as the CRAP input and behind --verbose; the --max-complexity* flags and both cyclomatic caps are retired in favour of per-kind --max-cognitive[-jsx|-behavior] caps. On a COG violation the CLI emits an actionable annotation naming the top increment sites, the deepest-nesting pocket, and the estimated reduction from flattening it. Caps calibrated from a fresh full-repo CC run (behavior 14, jsx 25) — net-looser than the retired cyclomatic cap while still tripping the deeply-nested tail. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Register blockscout#3663 follow-up tickets 07 (CC calibration) and 08 (gate outside src) Ticket 07 is deferred with a brief and a research handoff of the ticket-06 calibration findings — the distribution, the constraints that pinned the current caps, and the open questions for pushing them further. Ticket 08 is registered unscoped: extend the CC & CRAP gate to code outside src/ (e.g. tools/). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Scope blockscout#3663 ticket 07: quadratic nesting + ?? exclusion, cap 20 Turn the deferred CC-calibration ticket into an implementable spec. The investigation resolved to two model changes — exclude null-coalescing from CC (SonarSource conformance, extends ADR 0004) and penalise nesting quadratically (1+n², a deliberate divergence) — plus recalibrating the behavior cap 14 -> 20 at the resulting natural gap. Appends the session-2 decision and reproducible calibration targets to research.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Penalise nesting quadratically in cognitive complexity, exclude `??` Two coupled changes to the CC readability model, so the gate isolates nesting instead of punishing breadth: - Nesting structures charge `1 + n²` instead of `1 + n`. Identical at depths 0 and 1, biting only from the third level in, so flat and shallow code is untouched while genuinely-nested code accelerates away. A deliberate divergence from SonarSource. - `??` / `??=` no longer increment. The SonarSource paper ignores null-coalescing as readable shorthand, in the same class as `?.`, so counting it was a conformance bug that inflated pure-defaulting functions. Recalibrated the per-kind caps on a fresh full-repo run: behavior 14 → 20 (the natural gap between the flat-breadth band and the nested band, firing on 8), jsx re-confirmed at 25. Together they fire on 24 vs the retired cyclomatic gate's 43. The flattening-saving estimate in the GitHub annotation is corrected to `2·depth − 1` per structure, which the linear model had hardcoded as 1. ADR 0005 records the divergence and its coupling to the caps. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Scope blockscout#3663 ticket 08: extend the CC & CRAP gate to tools/ Allowlist src/ + tools/ over five extensions; deploy/**, test support and repo-root files stay out with stated reasons. Fixes the two offenders the gate finds in its own implementation; the tools/ .mjs scripts are deferred to blockscout#3674 and deploy/tools/'s lint + type-check gap to blockscout#3675. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Extend the CC & CRAP gate to tools/, and fix its own two offenders The scope rule becomes an allowlist of src/ and tools/ over five extensions, rather than a src/ prefix test that let everything else fall out by accident. deploy/** stays out until it is under ESLint and tsc (blockscout#3675); test support and configuration stay out on the same grounds specs do. Measured against the existing caps: tools/ adds 185 behavior functions and no jsx ones, and the 132 in TypeScript top out at exactly CC 20 with none over, so the caps stand unchanged. Only the three .mjs scripts break (blockscout#3674). The gate now scores its own implementation, which cost two functions: complexity.ts's `visit` (COG 31) loses its if-chain pocket to a `visitIfStatement` helper, and index.ts's `parseArgs` (CRAP 240) becomes a cursor walk over a discriminated-union flag table, covered by a new index.spec.ts. That table also removes the prefix-shadowing hazard the old startsWith chain only survived by arm order. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Restructure the code-complexity docs, move its ADRs into the tool CONTEXT.md had grown to 285 lines and restated the same rationale in up to five places: the jsx/no-CRAP argument, the quadratic-nesting argument and the missing-coverage rule each appeared across the doc, config.ts and three or four module headers. Paraphrase duplication drifts silently, and it already had — crap.ts documented a CRAP threshold of 30 when the cap is 80, and the scope exclusions were "three categories" in scope.ts but "two" in CONTEXT.md. Give each meaning one owner: - CONTEXT.md (285 -> 158) is the hub: what the gate is, a new "the gate failed — what now" triage section, function classes, modes, scope. - SCORING.md holds the counting model, with a worked example verified against the tool, and the divergences from SonarSource and ESLint. - CALIBRATION.md holds the caps, the distribution behind them, and a re-tune procedure that did not exist anywhere before. - config.ts (49 -> 18) keeps the numbers and drops the calibration history. - Module headers state what the file does plus local gotchas; rationale that has an owner doc becomes a one-line pointer. The flag list comes out of CONTEXT.md in favour of --help, which already covers every flag. The two ADRs move to tools/code-complexity/adr/, renumbered 0001/0002 — they constrain only this tool, so a reader working elsewhere never needs them. AGENTS.md gains the local-ADR convention (numbered per folder, repo-wide index unchanged, vacated numbers not reused), and its per-directory pointer now leads with cognitive rather than cyclomatic complexity. Also drops 13 spec/ticket references from the source: implementation traceability that resolves to nothing for a later reader. The doc-link checker only walks files named CONTEXT.md, so the new siblings are unchecked until blockscout#3674, whose scope now covers extending it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * update ADR section in AGENTS.md * Prune task working files for the code-complexity CI gate task The spec still described the cyclomatic-first gate that the first four tickets built. Tickets 05-08 made cognitive complexity the readability gate, demoted cyclomatic to a CRAP input, split the cognitive cap per function class and moved CRAP eligibility from files to functions - none of which the spec absorbed. Since the FRs are the contract the whole-task review reads, bring them to the shipped model and drop the points that were only restating the implementation (the doc split, the increment table, the report columns). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * [skip ci] add CC and CRAP to rules * Resolve review findings on the complexity/CRAP gate F1 — a skipped vitest run no longer disables the CRAP gate. `resolveCoverage` returned null when nothing in the selection needed coverage generated, and analyze.ts reads null as `--no-coverage`. Generation scope thereby decided which functions were scored (contradicting FR5), and a function's score depended on what else shared its selection. Skipping the run now yields empty coverage; only `--no-coverage` yields null. F2 — `--coverage-file` tolerates a missing report. CI's `vitest --changed --passWithNoTests` writes no coverage-final.json when it selects no specs, so the gate step died on ENOENT instead of scoring the untested code at 0%. Both readers now share `readCoverageOrEmpty`. F3 — stop ignoring `tools/**` in the Checks workflow. `tools/` is in the gate's file scope so it scores its own implementation, but a tools-only PR skipped the workflow entirely and was never gated, linted or type-checked. F4 — move the ADR mechanics out of always-loaded context into `.agents/rules/adr.md`, leaving AGENTS.md the locations, the index and a pointer. Restores the instruction to add a line to that index. F5, F6 — drop a diff-narrating test comment; point both ADRs at SCORING.md, which owns the counting conventions CONTEXT.md only links to. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * [skip ci] typo fix --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Enforce react/jsx-filename-extension to keep JSX in .tsx files
Enable react/jsx-filename-extension with { allow: 'as-needed',
extensions: ['.tsx'] } so .tsx files must contain JSX and JSX-free
logic/hook files use .ts. Rename 84 JSX-free .tsx files to .ts.
The rule is syntactic-only (~0.1% of lint rule time), so it has no
measurable impact on lint speed.
Closes blockscout#3668
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Update doc references to renamed .ts files
playwright/lib and useNavItems moved from .tsx to .ts; fix the two
agent-doc references so lint:doc-links resolves.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Fix OP withdrawal claim after connecting a wallet on L2. useWalletClient was pinned to the parent chain and stayed undefined after switchChain; send the claim through useWriteContract so the client is resolved at mutation time. Co-authored-by: Cursor <cursoragent@cursor.com> * Surface batch-transaction recipients in the tx details To & Value fields (blockscout#3664) * Add spec for tx details "to" & "value" UI/UX changes (blockscout#3661) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Restyle token-transfer "View all" link on tx details Remove the leading navigation/tokens icon before the "View all" link in the token-transfer detail rows and apply the secondary link style. The overflow show-condition and Token-transfers-tab target are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Surface batch recipients in tx details "To" and "Value" For an Eden sponsored (batch) transaction with more than one unique recipient, the "To" field now lists the distinct recipients (first row rich, rows 2+ bare address, capped at 5 with a "View all (N)" link) and the "Value" field appends a "to N recipients" link. Recipients are de-duplicated by address; both links expand the Details section. Extract the "To" field into TxDetailsTo, derive the breakdown in a shared getBatchRecipients helper, and add an endContent slot to DetailedInfoNativeCoinValue for the "Value" link. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Prune task working files for tx details to/value task Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Fix batch-recipient counting and rendering in tx details "To" Exclude contract-creation calls (null `to`) from the unique-recipient count so "N recipients" / "View all (N)" reflect addresses only (FR-6), and render the recipient list by matching the top-level `to` by hash instead of assuming it is the first call — keeping the shown rows a subset of the counted recipients in every case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…#3677) * Restructure the code-complexity tool into pipeline stages The tool was 27 flat entries in one directory with four doc files scattered among them, and CONTEXT.md served four different readers in one linear scroll. Group the sources into a directory per pipeline stage: select/ (scope, diff), measure/ (complexity, jsx, crap), coverage/ (read, generate), render/ (report, github). index.ts, config.ts and analyze.ts stay at the root. Rename coverage.ts and generate-coverage.ts to coverage/read.ts and coverage/generate.ts so the paths stop stuttering. Specs move with their sources; no logic changes. Split the docs by reader question and collect them under docs/: MODEL.md (renamed from SCORING.md, now also carrying the scope rules and the jsx/behavior classification), RUNNING.md (new: the two selection axes, generation scope, the report, CI wiring) and CALIBRATION.md. adr/ stays at the module root, per .agents/rules/adr.md. CONTEXT.md drops to an entry point: a routing table, the gate-failed triage, and a file map grouped by stage. Reframe the tool as a code-quality gate rather than a CI gate, and say plainly that select/, measure/ and coverage/ never see a cap: the thresholds enter once, in analyze.ts, and everything downstream reads the verdict it stamps. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * add vscode tasks * Fix doc sediment and pointers in the code-complexity tool Resolves the four review findings on the restructure: - drop the function-class summary from CONTEXT.md (wrong rule, and MODEL.md already owns classification) - retarget MODEL.md's scope pointer to ../select/scope.ts, and drop the circular back-reference to MODEL.md from CONTEXT.md's scope.ts line - drop CALIBRATION.md's references to a distribution and neighbour outcomes the restructure deliberately removed - state the run.sh cwd split in RUNNING.md instead of equating the wrapper with tools/dev-server/fetch.sh Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d indexer (blockscout#3679) * Add spec for scoping interchain requests to the deployment's slice Issue blockscout#3650: one shared interchain-indexer instance replaces the per-frontend ones, so every request has to declare its own slice — unknown query params are silently ignored, which turns a dropped filter into a cross-frontend data leak that returns 200. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Break blockscout#3650 into four tickets Two prefactors that can start now — the interchain types bump with its fixtures, and the two bridge-scope env vars — then the central request scoping and the message-details route move as independent branches. Q03 now also tracks making BridgeInfo.id non-optional: it is optional in 1.8.1 although always sent, and the new route needs it to build a link. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Type the app against interchain-indexer-types 1.8.1 The shared indexer's contract adds the filter vocabulary later tickets send (home_chain_id, bridge_ids, include_unindexed_chains, bridge_id on message details) and two response fields, BridgeInfo.id and has_unindexed_chain. The bump is additive, so nothing had to be reconciled to make it compile. Stubs and mocks now carry both fields, so links and requests built off them in later tickets look like the real thing. A message whose destination side never resolves must still render as an ordinary row with that side empty. The list components already guard each optional field on its own; the new specs pin that, including a row that is not flagged and still has no destination hash — which is what fails if a later change reaches for has_unindexed_chain as a shortcut for "this row is complete". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Take the deployment's bridge scope from env The interchain indexer is being consolidated into one shared instance per environment, indexing every bridge and every chain. A deployment therefore has to state which slice of it belongs to it, and it must not boot without one: unknown query params are ignored by the service, so an unscoped request returns another deployment's traffic with a 200 rather than an error. NEXT_PUBLIC_CROSS_CHAIN_TXS_BRIDGE_IDS is required whenever cross-chain transactions are enabled, and rejected otherwise. NEXT_PUBLIC_CROSS_CHAIN_TXS_INCLUDE_UNINDEXED_CHAINS is the escape hatch for rows whose counterparty chain no bridge indexes, off by default. The names are not multichain-specific because a single-chain deployment spanning two bridges is expected. Nothing consumes the values yet. They are read outside the feature config's isEnabled branch: the Playwright mockApiResponse fixture builds its route matcher in Node from playwright/.env.pw while the component reads the envs mocked in localStorage, and NEXT_PUBLIC_CROSS_CHAIN_TXS_ENABLED is only ever set per test, so a resolver gated on isEnabled would resolve no bridge ids on the matching side once the requests start carrying them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Name the real check commands in tickets 03 and 04 The criteria said `pnpm test` and `pnpm lint`, neither of which is a script in this repo. Spell out the vitest, lint and complexity commands, and point the Playwright criteria at the Docker runner — local runs diff on rendering. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Scope every interchain request to the deployment's slice Each interchainIndexer resource declares which scope params its endpoint accepts, and buildUrl merges the resolved profile into the query string — one funnel, so no call site can pass or forget the params itself. Resolvers are keyed by service as well as param name, so a same-named filter on another API cannot pick up interchain logic and an unresolvable name is omitted rather than fatal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Carry the bridge in the message-details URL The shared indexer can hold the same message_id on more than one bridge, so an unqualified lookup is a guess. The route becomes /bridge/[bridgeId]/cross-chain-tx/[id] and the details request sends bridge_id, which makes the page resolve identically on a fresh load, a reload and a pasted deep link. A row with no bridge id renders unlinked rather than falling back to a guessed route, and a malformed message id is re-thrown as 422 so it lands on the invalid-input page instead of the 500 copy. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Take interchain-indexer-types 1.8.2 The version the service now runs scopes /interchain/chains by bridge_ids, so the counterparty selector lists the chains the deployment's own bridges index instead of every chain on the shared instance. The resource already declared the param, so the bump is the whole change; BridgeInfo.id and has_unindexed_chain becoming required reconciles with no edit, since ts-proto still renders the parent message fields as optional. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix pw tests * Prune the ticket breakdown for blockscout#3650 The decomposition is preserved in git history, one commit per ticket, so only the spec survives in the tree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Resolve review findings on the scope-filter work Cover FR4 with invariants rather than a copy of the registry: every interchain resource must declare a scope (bar message details, which deliberately takes none), and every declared name must have a resolver. Pinning the exact filters per resource would have to be edited in lockstep with every legitimate change, so it would only ever catch a deliberate edit — code review's job. Reword FR4 to that intent and record why the stricter reading was dropped. Reuse the cluster-chain mock in the scope-filter spec instead of an unexplained double-cast; it has to name its indexer host, because getResourceParams prefers a chain's own app_config.apis over the deployment's. Gate INCLUDE_UNINDEXED_CHAINS on CROSS_CHAIN_TXS_ENABLED, matching its two siblings in the same schema, and resolve table cells by header text so the spec survives a column reorder. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add spec for diff-scoped mutation testing Spec for issue blockscout#3665. Measures assertion strength of the existing vitest specs: only logic mutators, only files with a co-located spec, diff-scoped to changed lines on PRs, failing when a mutant survives. Drops the review-changes "mutation-thinking" deliverable and the issue's snapshot rationale from scope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Break mutation-testing spec into tickets Seven vertical slices for blockscout#3665, plus progress.md. Rewrites FR12: the run is bounded by wall-clock time rather than a mutant cap. A count-based bound would have to track Stryker's own generator to stay honest, and diff size does not predict cost — runtime is governed by import-graph centrality. Results stream to disk as each mutant is tested, so a truncated run still reports what completed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Make StrykerJS boot on this repo Ticket 01 of the diff-scoped mutation testing task. No CLI yet — this is Stryker itself working here, which every later ticket assumes. Two pnpm integration blockers, both solved by configuration. Stryker's minimatch@10 needs a 5.x brace-expansion, while the repo pins the package globally to a 1.x security override; a scoped `minimatch@10>brace-expansion` entry lifts it along that dependency path only, following the existing vite-plugin-dts precedent. The global 1.x pin is untouched. Stryker also discovers plugins by globbing its own scope in node_modules, which pnpm's isolated layout defeats, so the vitest runner is named explicitly. stryker.config.json carries everything that does not vary per run: the logic-only mutator restriction (conditional, equality, logical, arithmetic, boolean literal — the other twelve classes excluded), ignoreStatic and a raised timeout for determinism, and the json + html reporters. The sandbox lives inside the tool folder; it and the reports are gitignored, and excluded from vitest, tsc and cspell, because a run that does not finish leaves the sandbox behind and it is a full copy of the repo. ignorePatterns is an allowlist rather than a denylist, so a new top-level directory is left out of the sandbox and fails the dry run loudly instead of being copied silently. Verified complete by running the whole suite inside the sandbox. The primed-request drift tests are excluded through a dedicated vitest config rather than through the sandbox: src/server/primedRequests/index.spec.ts globs those files off disk to assert every page has one, so removing them turns the drift check red and aborts the dry run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Add the mutation-testing CLI and its three selection modes Ticket 02 of the diff-scoped mutation testing task. `pnpm test:mutation-testing` decides which files to mutate, runs Stryker over them and prints a per-file score table read from the JSON report. Selection mirrors the complexity gate's flags: bare takes the full eligible set, explicit paths focus on those files, and `--changed[=<ref>]` scopes to the diff against a base ref through the merge-base, so the branch's own commits and uncommitted edits count while base-branch churn does not. A file is eligible only when a vitest spec sits beside it — an untested file would otherwise produce a run of unkillable mutants rather than findings. When the selection comes out empty the CLI says why in one line and exits 0 without starting Stryker at all. The diff resolution and scope allowlist are imported from the complexity gate rather than reimplemented. Its `hasCoLocatedSpec` moves out of that tool's index into `select/eligibility.ts` so both tools read the naming convention from one place instead of keeping two copies in sync. Output is built from Stryker's JSON report: the clear-text reporter prints every mutant with its full diff and is unusable at this scale. Timeouts count as killed and the excluded mutator classes stay out of the denominator entirely, which is how Stryker scores it — otherwise the twelve disabled classes would dominate every row. The report is deleted before each run, so a run that dies reports nothing instead of presenting the previous run's results as fresh. The sandbox allowlist grows a `tools/**` entry, since the tool's own files are the first thing worth mutating and the gate is expected to cover repo tooling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Mutate only behavior code, only on changed lines Narrow what Stryker mutates from whole files to line ranges: the complement of every `jsx` function body, intersected under `--changed` with the lines the diff touched. Ranges restrict mutant creation rather than filtering findings afterwards, and a file left with no mutable line is dropped from the selection. Classification comes from the complexity gate's AST classifier, and a line belongs to the innermost function containing it — so a handler inside a component is still mutated, while a render callback inside a hook is not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Bound a mutation run by wall-clock time Ticket 04 of the diff-scoped mutation testing task. A selection that reaches deep into the import graph can no longer grind for hours: --budget bounds any single run, default 15 minutes, kept in the tool's config module so a local run and a CI run bound identically. Stryker writes its json report only when a whole run finishes, so a stopped run would leave nothing behind. A reporter plugin closes that gap by appending one JSON record per line as each mutant is tested, and the CLI reads that stream back when the run did not finish. Line-delimited because a process killed mid-write leaves a half-written last line, and every complete line before it must still parse. Appended synchronously, one open-write-close per record: a buffered stream would be the obvious choice, but Stryker answers a signal with process.exit and drops whatever has not been flushed — exactly the records that matter. The plugin is a .mts file, so emitted as ESM, because Stryker loads a plugin through import(); it is named by path in the config's plugin list, since pnpm's isolated layout defeats Stryker's plugin globbing. A complete run is untouched: it still reads Stryker's own report, and the choice between the two sources is a pure function whose spec asserts neither path reads the other's file. Stopping the run kills the process group rather than the process. Stryker exits on a signal without disposing its forked vitest workers or its sandbox, so the run is spawned detached, SIGTERM goes to the whole group, anything surviving the grace period is killed outright, and the sandbox is removed by hand. Detaching also puts the run outside the terminal's foreground group, so SIGINT is forwarded — without that, quitting the CLI would leave the run behind. A budget expiring while Stryker tears down does not count as truncation: the json report is the honest test of whether a run finished, and the notice is keyed off that rather than off having sent the signal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Report survivors grouped by source line Split the mutants off Stryker's per-mutant status field: survivors are listed under their file, grouped by the line they sit on, while no-coverage mutants are counted on one line, being a coverage gap the CRAP gate already owns. The HTML report's location is printed once the run has written it. The selection layer now takes the repository as an argument instead of calling process.chdir(). Stryker runs vitest in worker threads, where chdir throws, so the spec that used it was failing the dry run of every mutation run rather than only its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Fail a PR when a mutant on a changed line survives The gate runs diff-scoped in its own CI job, chained on the unit tests because a red suite makes Stryker abort during its dry run. Survivors become inline ::error annotations on the diff; a no-coverage mutant does not gate, being the CRAP gate's finding. Suppressing a mutant stays possible but has to be a reviewable claim: an ESLint rule accepts only a next-line-scoped disable that names its mutators and gives a reason. It lives beside the tool it enforces, where a co-located spec both tests it and makes it mutation-eligible itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Document the mutation-testing gate Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Kill the gate's survivors on its own code Turning the gate on left 18 survivors in the tool it gates. Half were missing assertions: the flag parser never proved a value flag consumes the following token only when it has no inline one, the selection layer never reached full mode's empty exits, the tie-break between two functions covering the same lines was never exercised in either direction, and the report's ordering was asserted from input that was already sorted. The other half were dead code, which the suppression policy says to delete rather than defend. The table's hand-rolled padding guarded a case the column widths rule out; the string comparator's equality shortcut was unreachable because both call sites compare unique keys, so the default sort does the job and stays locale-independent; the stream's blank-line filter dropped lines that fail to parse anyway; and the innermost-function search compared spans that the traversal order already settles, so a tie is now what it means — the same lines. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Escape the backslash when making a path literal Minimatch's escape character was the one glob metacharacter the escaping left alone, so a path containing a backslash had it read as escaping the next character instead of as itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fail a run that never tested a mutant A run stopped at its wall-clock budget reports whatever it got through, and the exit code looked only at the survivors in that partial report. A dry run that filled the budget gets through nothing, so it produced an empty report, no survivors, and a green check — while the truncation notice beside it said "none of the selection was covered". The warning annotation covers a partial run staying green on purpose; it does not cover having tested nothing. The verdict now asks the run how far it got as well as what it found. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Put a CI run's report where it gets read A job log opens below checkout and install output, with Stryker's own narration on top of that. The report now lands in the job summary, which renders on the run page; Stryker's logging is folded into a collapsed group rather than silenced, since it is the diagnosis when Stryker fails. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Keep Vitest's own job summary out of the gate's Vitest appends a github-actions reporter to its defaults whenever $GITHUB_ACTIONS is set, and Stryker ends one test run per mutant — so the run page filled with hundreds of "Vitest Test Report" blocks above the report, each killed mutant reported as a test failure. Naming a reporter in the config Stryker drives is what keeps that reporter out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eed (blockscout#3681) * Let operators pick Flashblocks or Subblocks naming for the OP Stack feed OP Stack chains are split between the legacy "Flashblocks" name and the newer "Subblocks" name while streaming the same wire format, and one frontend image serves both. The name was hard-coded (`subblock` since blockscout#3643), so a chain that still runs Flashblocks had no way to keep its label. - NEXT_PUBLIC_FLASHBLOCKS_NAME (`flashblock` | `subblock`, default `subblock`) now drives the `name` field of the flashblocks feature config, which every user-facing string already derives from. MegaETH keeps `mini-block`. - The feature config also carries the blocks-page tab ids: the configured name first, the other OP Stack name as an alias, so links written under either name keep resolving. - A `blocksTab` getServerSideProps guard redirects the alias to the canonical id with a temporary redirect, preserving the other query params. Temporary because the operator can flip the name later. - Docs, a flashblocks validator sub-schema (the name requires the socket URL), test preset, glossary, and vitest specs for the config and the guard. * Address review: literal tab ids, guard rename, spec via withEnvs - Tab ids are written out literally as FLASHBLOCKS_TAB_IDS / FlashblocksTabId and the feature's `tabIds` is a non-empty tuple, so the ids are greppable and the guard's destructuring of the canonical id is sound. - The guard is named `blocks` after the page it sits on, like its siblings. - The guard spec uses `withEnvs` again instead of injecting a stub config. - Rationale comments dropped from the code; docs/ENVS.md is the single place that states it. The glossary row keeps the rename etymology and no longer mentions the default. * Address review round 2: drop the env tuple cast, narrow the MegaETH tab id type
He owns the ERC-8056 API work and is the routing target for its questions, but was missing from the directory. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… and a restyled PR review (blockscout#3690) * Make the review skills' inputs explicit, and add a review-file mode Chat mode is gone — it was never used, and inferring the mode from whether a PR existed meant a sandbox with no network silently reviewed into the void. `review-changes` now takes its output as a required positional and everything else as a flag: `--round` (which retires the "prior comments mean arbitration" heuristic), `--scope`, `--as`, `--ticket`. `resolve-review` mirrors the output input, and both carry an `argument-hint` so the `/` picker states the form. `md` output writes the review into the task folder instead of onto a PR: one file, one thread per finding, replies appended as blockquotes and a `Status:` line edited in place. `--scope uncommitted` reviews the working tree before the ticket is committed and writes beside the ticket; `--scope branch` writes at the task root. Both are pruned at land, like the rest of the scaffolding. Where the agent cannot tell whose the uncommitted changes are, it asks rather than guesses. `--as <tag>` is what lets two agents review one change at once: it prefixes the finding ids and the review file name, and a follow-up round rules only on its own tag's findings. Untagged stays the default, since one reviewer is the usual case, and a tag derived from the running model would change under the model and orphan round 1's threads. A first round that finds someone else's review already posted stops and asks for a tag. SKILL.md is 229 lines down to 180, holding the spine only: the axis briefs and the finding shape move to axes.md, where the axis subagents read them directly instead of having them re-transmitted, and the publishing mechanics split into output-pr.md and output-md.md. Dropped along the way: the `gh pr view` exit-code rationale that gh-commands.md already carried, and the mode-detection prose the inputs replace. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Drop bot reviewers from the review skills Reviews are done by agents running these skills, not by PR bots, so the bot source, its `user.type` test and the bot status-post noise all described a path that no longer exists. The `— Reviewed by …` footer is now the only test separating this workflow's own review from a human's. Also delete `.cursor/BUGBOT.md`, dead config once Bugbot stopped reviewing, and align the out-of-bounds comment bullet with the hardened comments rule in `code-quality.md`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Restyle the PR review output Severity emoji on every finding title, id and severity alone on the first line. Findings with no diff anchor move out of the review body into their own issue comment. The summary is a status line plus open counters, the same shape in every round, with no findings table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Close out the review of the review skills Seventeen findings from two concurrent reviewers, fixed across the skills and their reference files. Two loops could not close. The `--as` guard stopped a first round whenever any tagged review existed, so the concurrent-reviewer case it was written for was unreachable; it now stops only when the runner has no tag of its own. And the `md` · `branch` clean-tree precondition applied to every round, while `resolve-review` leaves its `md`-mode fixes uncommitted on purpose — the follow-up round that exists to verify those fixes always found a dirty tree and stopped. A `md` follow-up now has no clean-tree precondition. Deferred nits had no ruling anywhere, so a follow-up round could never satisfy its own done-when once a nit was open: both ruling tables gain a `deferred` row, and the `md` status keeps such a finding out of the next Gate 1. Bots get their `user.type` row back — CodeQL and Copilot still reach a PR, and without the row their comments were unrejectable. `md` mode drops `needs-human` instead: only agents read that file, so the escape hatch there is `resolve-review`'s Gate 1, a terminal exchange with the developer. The rest are contract mismatches between files that have to agree: the non-anchorable split pattern now accepts a tag prefix, the follow-up ruling comment carries the footer that marks it as a ruling, `headRefOid` joins the discovery fields, `code-reviewer.md` gates on `cleared` and reads back a location step 5 now records, `resolve-review` takes `--scope` and `--ticket` so its review file is named rather than inferred, and the outcome rule and the footer rationale each live in one place with the others pointing at it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix the review skills' source split and tagged ruling ids Three findings from the second review round, all regressions from the first round's fixes: - `gh-commands.md` still claimed the footer was the only source test and that every non-footer comment was a human's, contradicting the restored `user.type == "Bot"` row it is cited as the rationale for. Scope that claim to workflow-vs-human and point at `resolve-review/SKILL.md`'s table for the full split. - `deferred` was missing from the threads to leave unresolved, so an agent would close a deferral the round had just granted. - Non-anchorable ruling lines were written bare `F<n>`, which matches no tagged finding id and would be ambiguous between two reviewers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…3691) * Let operators pick which color themes an explorer offers New optional NEXT_PUBLIC_COLOR_THEMES lists the themes an instance exposes; unlisted ones are hidden, and a single-theme list drops the switcher entirely, so an explorer like Arc can offer light and dark only. The list filters the canonical theme order rather than reordering it, and it feeds default resolution: when the available themes cannot cover prefers-color-scheme, colorTheme.default becomes the darkest available theme, so a narrowed instance renders SSR-correct instead of flashing the wrong mode until the client effect corrects it. NEXT_PUBLIC_COLOR_THEME_DEFAULT stays as it was — deprecating it would force a config migration on every deployment — and the validator now rejects a default that the list does not offer. SettingsColorTheme keeps the theme-applying effect it always had and only skips its own markup below two themes: the popover mounts it eagerly, so a hidden switcher still writes the CSS vars and primes the cookies. The middleware resets a cookie holding a theme the instance no longer offers, mirroring addressFormat. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Keep a returning user's color mode when their theme is dropped The middleware reset an unavailable COLOR_THEME cookie to the configured default, but a theme list covering both color modes leaves that default undefined, so the reset fell through to themes[0] — light. A returning midnight + dark visitor was flipped to light for good: the Set-Cookie lands before JS, so SettingsColorTheme finds light available and never re-resolves against the color mode. Honour the COLOR_MODE cookie where there is one, and document the implicit default that config.ts derives when the offered themes cover a single mode. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… supported chains (blockscout#3693) Use the Pro API in Hardhat and Foundry verification instructions Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…sted and gated (blockscout#3694) * add the task spec for rewriting the tools/ .mjs scripts in TypeScript Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * break the tools/ TypeScript rewrite into four tickets Three independent ports on the frontier — the preset-list sync tool, the React profile aggregator, and the doc-link checker as tools/doc-links/ — plus the doc-link scope extension behind the rewrite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Rewrite the preset-list sync tool in TypeScript Moves tools/dev-server/sync-preset-lists.mjs to tools/dev-server/sync-presets/, bringing it under pnpm lint:tsc and the unit suite so the complexity gate reports real coverage instead of 0%. Behaviour is unchanged: same arguments, same output, same exit codes. The tool gets its own tsconfig.json rather than reusing tools/dev-server/'s — adding an outDir there would relocate fetch.js out from under the Dockerfile's COPY. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Rewrite the React profile aggregator in TypeScript Moves tools/profiling/aggregate-react-profile.mjs to aggregate.ts plus an index.ts CLI entry, bringing it under pnpm lint:tsc and the unit suite so the complexity gate reports real coverage instead of 0% — replayOperations scored CRAP 182 purely because nothing tested it. Behaviour is unchanged: verified byte-identical output against the old script across single-file, compare and every option combination. Specs run on hand-built wire-format fixtures rather than a committed DevTools export: an export would pin the fixtures to one DevTools version, and the wire format is exactly what is under test. The ADD record handling moves into its own function. A faithful port left replayOperations at cognitive complexity 20 against a cap of 20, so any later edit to it would have broken the gate. Carries the round-1 review resolution: the .mjs's usage/options header, the type section banners and the fixture slot-layout comments are gone, the CONTEXT.md file table is back to the importable-vs-CLI split, and the two CLI defaults are named constants. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Rewrite the doc-link checker as tools/doc-links/ Split the checker into modules by concern — surface collection, line stripping, reference resolution, per-line checking, reporting — with the CLI in its own entry file. Decomposing the per-file check brings its cognitive score from 58 to 13, under the cap of 20. The repo root now comes from git rather than from hops counted off the entry file, which runs one directory deeper once compiled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Check every markdown file beside a CONTEXT.md, not just that file A directory documenting itself across several files got one of them checked, so the relative references inside the others could break silently. The collection step now takes every tracked markdown file in and below a CONTEXT.md directory, nested subdirectories included, which brings the two ADRs and three docs under tools/code-complexity/ and the three under tools/mutation-testing/ into the surface — 68 files to 76. Two of their references did not resolve, both writing ./run.sh from a docs/ subdirectory where the file sits one level up. Fixed as docs: the resolver still resolves ./ against the file's own directory, because that is what ./ means everywhere else in the instruction surface. src/toolkit/package/ is exempt by path in the existing exclusion list — its readme addresses npm consumers rather than agents. A README.md filename rule would instead unprotect every module readme that .agents/rules/docs.md expects checked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Prune the working files of the tools/ TypeScript rewrite task Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Name the DevTools op codes and drop the restating comments Three review nits on the tools/ rewrite: - `check.ts` and `resolve.ts` opened with headers that restated their row in `tools/doc-links/CONTEXT.md` word for word, and `index.ts` restated it before getting to why the checker exists. The role has one home now. - The `strip.ts` row named `ILLUSTRATION_FORMS`, which the constraint below the map already owns. - `replayOperations` switched on the raw op codes 1-7 with comments that said what a name would. The operand layouts stay — they are what makes the index arithmetic legible. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Kill the surviving mutants in the rewritten tools/ files Two of them marked redundant code rather than a missing assertion: the `endIndex === -1` operand in the marker guard is unreachable behind the ordering test, and comparing a path to an excluded entry adds nothing to the prefix test beside it. The rest were assertions the specs never made — link forms without an anchor or a slash, the boundary of the string-table range, a zero delta, and skip-width fixtures whose operands doubled as op codes, so a mis-skipped record resynchronised on its own operands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
) * Let operators set the label on the DeFi dropdown button The trigger's text was hardcoded to "Blockscout DeFi", so an instance could configure the dropdown's actions but not what the button calling them is named. Resolves blockscout#3687 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Address review on the DeFi button text env Gate the button-text env on the items env with `.when(...)`, the companion pattern the validator's CONTEXT.md prescribes, and annotate the spec helper's return type. Note why the config spec primes `src/config` first: without it the import graph is entered mid-cycle and `app.baseUrl` reads undefined. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Enforce the app-config convention with ESLint rules Three rules, all errors: a config module has no export besides its default; a config module is imported only from another config.ts, src/config/** or its own config.spec.ts; src/config/utils/envs.ts is imported only from a config.ts or src/config/**. Scope is every module the root aggregator assembles. The convention and its exemptions are documented in src/config/CONTEXT.md. eslint-plugin-boundaries had no import resolver, so the pre-existing src-api boundary rule never fired; wiring eslint-import-resolver-node revived it and surfaced two pre-existing violations, marked with explanatory eslint-disable comments. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hv8kJu53gsF1oCGfWhPT1i * Resolve review findings on the app-config lint rules - keep the api layer policy on src/api/config.ts by selecting the rule's source by path rather than by element type (F1) - document the real destination for values moved out of a config module, and match the lint message (F2) - point at env-vars.md for the config sections; shorten the import message; describe the config.spec.ts exemption by file name (F3, F4) - explain the fixture disable in scope-filters.spec.ts (F5) - restrict direct window.__envs reads outside getEnvValue (F6) - write the companion globs as placeholders so doc-links resolves them Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hv8kJu53gsF1oCGfWhPT1i * [skip ci] Keep the window.__envs restriction on where process.env is allowed The override for pages, server, playwright and tooling files turned the whole no-restricted-properties rule off. Relax only the process.env entry there; the raw browser env map stays readable by getEnvValue alone (F7). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hv8kJu53gsF1oCGfWhPT1i --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…#3700) * Spec ERC-8056 token multiplier support Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PA4HAJdnnF3MTCmfjTShxm * Break ERC-8056 token multiplier support into tickets Six scoped tickets, two deferred briefs (history tab and inline history, blocked on the endpoint shape), progress.md, Q03 waived, and the .env.extra additional-types override so dev and demo deploy enable ERC-8056. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PA4HAJdnnF3MTCmfjTShxm * Add ERC-8056 as a fungible token type with multiplier primitives Pin @blockscout/api-types 0.0.1-beta.9aecddc69c, which carries the ERC-8056 multiplier fields on the token model and on fungible transfer totals, and drops total_gas_used from the stats response. Add the one predicate that resolves a token to its UI multiplier (only when the instance enables ERC-8056 and the response carries the field) and the one formatter for the factor, with unit specs. Default the new token model fields, add ERC-8056 token and transfer mocks, and a shared env preset. Search results branch on the fungible-type predicate instead of a hardcoded ERC-20 check, so an ERC-8056 token shows its price cell and lands in the Tokens group. The group heading lists the enabled fungible types. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PA4HAJdnnF3MTCmfjTShxm * Scale ERC-8056 amounts in the shared primitive; holders and token details calculateUsdValue, AssetValue and SimpleValue accept a multiplier: the displayed amount is raw × multiplier, fiat follows the scaled amount, and the tooltip discloses the raw value and the factor. Callers that pass nothing render exactly as before. Add TokenMultiplierTag in the token slice and wire the two first surfaces: the Holders quantity column (scaled, tagged) and the token details page — scaled total supply and a Multiplier row under Price. The NFT marketplaces and dapp rows now key off the fungible-type predicate instead of a hardcoded ERC-20 check, so an ERC-8056 token no longer shows them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PA4HAJdnnF3MTCmfjTShxm * Scale ERC-8056 balances on the address page; tokens tab, net worth, token select The three address surfaces that computed token quantities on their own now go through the shared calculateUsdValue with the token's UI multiplier: the fungible tokens table and list (scaled quantity with the multiplier tag, USD from the scaled amount), the net-worth aggregation in the address token utils (renamed to addUsdValue), and the token-select dropdown row. The fungible tab is titled "Fungible tokens" instead of joining the enabled type names. Add an ERC-8056 balance mock and Playwright cases for the tokens tab and the token select, with a unit spec for the net-worth helper. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GmCfLhjVc7hFsuEUheVn3L * update tokens and nfts tabs labels * Resolve Q01: API always populates the historical ui_multiplier Verified on eth-sepolia; null stays a data gap that the predicate degrades to unscaled rendering. Tickets 03, 04, 06 now state that behaviour directly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GmCfLhjVc7hFsuEUheVn3L * Scale ERC-8056 amounts on token transfer surfaces by the transfer's own multiplier Token transfer rows (shared table/list, token tab, index page) and the tx-details snippet now pass total.ui_multiplier through the token slice predicate, so the amount is scaled, tagged and disclosed with the factor that applied when the transfer happened. A null factor degrades to plain ERC-20 rendering with no fallback to the token's current multiplier. TokenTransferSnippet routes every fungible type to the fiat snippet instead of matching ERC-20 only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GmCfLhjVc7hFsuEUheVn3L * Add a Multiplier column to the advanced filter and scale ERC-8056 amounts The advanced filter table gains a Multiplier column between To and Amount, showing the factor that applied to each ERC-8056 transfer row. The column is listed and checked by default only when ERC-8056 is enabled, read from the focused chain's config in multichain mode. The Amount cell scales those rows by total.ui_multiplier through the transfer predicate, which now also accepts advanced filter items. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GmCfLhjVc7hFsuEUheVn3L * Scale ERC-8056 amounts in transaction state changes by the state change's own multiplier The State tab renders token before / after / change through AssetValue, scaled by the state change's ui_multiplier (the factor in force at that block) via the token slice predicate, with the multiplier tag and the raw value disclosed in the tooltip. A null factor degrades to plain rendering with no fallback to the token's current multiplier. Full precision is kept in the cell so non-ERC-8056 rows show the same numbers as before. Bump @blockscout/api-types to 0.0.1-beta.082a03750a, which declares StateChange.ui_multiplier, and adapt to the rest of its changes: token counters gain ui_multiplier_changes_count, and OP dispute games gain l2_timestamp with a nullable l2_block_number (rendered as N/A). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GmCfLhjVc7hFsuEUheVn3L * Scope multiplier history tickets 07 and 08 against the published API types Resolve Q02: the ui-multiplier-changes endpoint and the counter are typed in the already pinned @blockscout/api-types beta, so no stopgap local type or follow-up migration ticket is needed. Replace the 07 and 08 briefs with ticket specs, and add Q07 for how a scheduled change is shown. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GmCfLhjVc7hFsuEUheVn3L * fix skeleton loading state when text leaked due to color prop override * Add a Multiplier history tab to the ERC-8056 token page The tab lists every recorded UIMultiplierUpdated event with its transaction, timestamp, block, old to new factor, activation date and derived status. It shows on a token of type ERC-8056 whenever the instance enables that type, and carries the ui_multiplier_changes_count from the token counters. The multiplier components move under components/ui-multiplier/ alongside the existing tag. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GmCfLhjVc7hFsuEUheVn3L * Show a Scheduled status for future ERC-8056 multiplier changes A change whose activation date is still ahead now reads Scheduled instead of Inactive, styled like Inactive as the designer approved (Q07). Active is still only resolved on the first page. The history table screenshot now covers all three statuses, and it runs on desktop only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Show recent multiplier changes inline on the ERC-8056 token details The Multiplier row gets a View history toggle when the token counters report at least one change. Expanding it fetches the history once and shows up to five of the most recent changes, with a View all link to the Multiplier history tab when there are more. On narrow screens the table scrolls horizontally. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Prune ERC-8056 token multiplier task working files Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix pw tests * Resolve review round 1 for the ERC-8056 token multiplier Pass the focused chain's app_config into every multiplier resolution so cluster routes agree with the history-tab gate; fix the ERC-20 branch on the cluster search item; show error and empty copy in the inline history; move ChainConfig to token/types/client. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * bump API types package * fix hydration error * fix pw tests * Support ERC-8056 token multiplier in multichain address portfolio Bump @blockscout/multichain-aggregator-types to 2.1.8 for the new ui_multiplier fields, apply the multiplier to quantity and USD value in the portfolio tokens table and net-worth calculation, and render the multiplier tag as on the single-chain address tokens page. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * update screenshots --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…review (blockscout#3726) * Add the spec for the Jev review-screen pilot (blockscout#3720) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Break the Jev review-screen pilot into tickets (blockscout#3720) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Add the review-screen rubric: ten standards rules with a vitest spec Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Add the review-screen CLI: standards grid, windows, sidecar `pnpm review:screen` resolves a change (branch or uncommitted scope, or an explicit base and spec), renders each touched file's hunks as line-id state, scores every rubric rule against every matching file with Jev, locates each over-threshold cell with a choice call, and prints the suspects as JSON. Every run writes a sidecar with all cell scores, timings and usage to the main checkout's .ai/jev/. No key or an API failure exits 0 with a status and reason. The spec grid is a stub until the next ticket. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Score each Functional Requirement against the diff in the review screen The spec grid asks, per touched file and window, whether the changes address each requirement of the task spec; a requirement whose best score stays below SPEC_THRESHOLD is a suspect with no line. Both grids share MAX_SUSPECTS, split evenly when both overflow. The task folder is excluded from the spec grid because its own spec files state every requirement and would hide the gaps. The sidecar records the spec cells next to the standards cells; a missing explicit --spec is reported in the spec block, not thrown. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Record suspect fates and finding origins in the review-screen sidecar `pnpm review:screen --origins <sidecar> --findings <path|->` reads the review's final findings table (JSON array or Markdown tables) plus the jev axis's drop list, matches each finding to the sidecar's suspects by file and window span or by requirement id, and rewrites the sidecar with an `origins` block: per suspect a fate (confirmed / merged / dropped with reason), per finding an origin (axis / jev / both). The block is replaced on rerun. The sidecar now records each window's line span and each standards suspect's window, which is what the match relies on once the diff is gone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Calibrate the review-screen thresholds against past diffs Screen seven past diffs (four merged PRs, two of them also at the commit their review was posted on, plus this branch at T04) with the new --calibration switch, which marks a sidecar as tuning data so --report can leave it out. Keep the standards default at 0.7, lower inline-empty-default to 0.6, raise the spec threshold from 0.3 to 0.7, and exempt a module's opening comment from the explanatory-comment rule. The evidence per value is in the ticket's notes.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Dispatch the jev axis in review-changes and record finding origins Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Add --report to review-screen: the pilot keep-or-kill table Reads every sidecar in the main checkout's .ai/jev/, leaves calibration runs out, lists sidecars without an origins block as pending, and prints per rule the suspects sent / confirmed / merged / dropped with the top drop reason, the jev / axis / both finding counts, and the added seconds and input tokens per review. --json prints the same data as JSON. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Prune the Jev review-screen task folder down to its spec Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Resolve review findings on the review-screen pilot - origins: each jev finding goes to one suspect only, nearest by located line - sidecar: record the screen's wall-clock span; --report uses it instead of summing overlapping calls - standards grid: rank cells and located suspects in grid order, name no suspects after a locate failure, keep a chosen line id only when it is in the window - spec grid: tie-break in grid order; skip with a reason when the task-folder exclusion leaves no file - hunks: StateLine as a discriminated union, no casts; shared EXEC_MAX_BUFFER in tools/cli - rubric: drop the module-banner and test-value exemptions - docs: usage errors exit non-zero and write no sidecar Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Copy .agents into the Stryker sandbox so the rubric spec can find the cited rule files The mutation gate's dry run failed on `rubric.spec.ts`: the sandbox only holds the allowlisted paths, and `.agents/` was not among them, so `existsSync` on every cited rule file returned false. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Kill the mutants the mutation gate found in the review screen Assertions added for the branches no spec pinned down; the redundant top-reason and split-hunk guards deleted; equivalent mutants suppressed with their reasons. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…tion pages (blockscout#3738) * Make the app action button a header element on token, NFT and tx pages, driven by metadata Drop NEXT_PUBLIC_TOKEN_ACTION_BUTTON_CONFIG (never released) and the token-action-button feature. The app action button from the metadata service's protocol tag now renders in the page header of the token, NFT instance and transaction pages, with {address}/{chainId}/{txHash} placeholders and utm params on the link. Tags whose meta carries hidden: "true" are read for the button but never rendered as tags. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix position of the button on NFT item page * [skip ci] temporary disable env validation for review env * Resolve review findings on the app action button - fill {chainId} with the viewed chain's id on multichain pages - pick the first protocol tag that carries an app action - skip hidden protocol tags in tx lists, hot contracts and tx details - type the mixpanel Action button source as AppActionSource - share the metadata response mock helper across Playwright tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cover the marketplace app-id branch in getAppActionData spec Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * merge tags and button in one container and move button to the title first row on tx page --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
… first visit (blockscout#3741) Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* Mock react-google-recaptcha in Playwright component tests Google's reCAPTCHA script occasionally slips past the global request interceptor, renders with the fake site key and fires onErrored, so the initialization warning randomly appears in TagSubmitionForm screenshots. Part of blockscout#3740 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Wait for Multisend chains info request before taking screenshot The widget requests chains info only after the lazy wallet stack mounts, so under full-suite load the token input's loader could still be visible when the screenshot stabilized. Part of blockscout#3740 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Re-hover AddressEntity before the hover screenshot In the test's small viewport the tooltip's first, not yet positioned frame covers the cursor. When the highlight delay expires during that frame, the entity is not hovered and the highlight is skipped for good, since React does not re-fire mouseenter when the pointer returns from the portaled tooltip. Re-entering the entity after the tooltip is positioned makes the highlight land every time. Part of blockscout#3740 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Wrap mounted component in TestApp so the mount locator always targets #root Playwright's mount locator resolves to the only child of #root, or to #root itself when there are several. When a component's first render is null (e.g. useIsMounted in TokenInstanceDetails), the color mode script is the only child, the locator sticks to it and the screenshot times out waiting for it to become visible. A display: contents wrapper keeps two children in #root without changing layout. Fixes TokenInstanceDetails base view and Address3rdPartyWidgets mobile base view. Part of blockscout#3740 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Use auto-waiting row assertions in address socket tests locator.count() does not wait, so the socket tests could count the loading skeleton rows (21 instead of 2) when the list response had not rendered yet. toHaveCount() and toHaveText() retry until the expected state appears. Part of blockscout#3740 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix pending transactions not updating from socket on address txs tab The socket handler replaced the updated transaction inside the cached list in place. React Query's structural sharing then found the new data deep-equal to the already mutated old data and kept the old reference, so the list never re-rendered and a pending transaction stayed pending. The handler now updates a copy of the list, and the socket test asserts the row actually updates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Re-hover EnsEntity protocol icon before the tooltip screenshot The first hover mounts the lazy tooltip, which remounts the icon and briefly swaps it with the image loading skeleton. With a still cursor the browser may leave the hover state on the removed skeleton, so the lazy open check fails and the tooltip never opens. Re-entering the settled icon opens the tooltip through a regular pointer event. Part of blockscout#3740 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Wait for the aborted chain logo in the ZetaChain icon stub test The stub replaces the image loading skeleton only after the global request interceptor aborts the unmocked chain logo request. Under load the abort could land after the screenshot stabilized on the skeleton. Part of blockscout#3740 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * update screenshot --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…lockscout#3736) * Switch default theme selection, navigation and top bar colors to neutral Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Align dark selected control and option colors with the design, update screenshots Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix screenshots * rollback colors for selected.option.bg * [skip ci] change styles for "select" tag * update screenshots * yet another colors update * update screenshots * fix screenshot --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…blockscout#3744) * Support the xDai bridge and native tokens in cross-chain transactions The interchain indexer v1.11.0 reports a token kind on cross-chain token objects and serves a null address for a native token. Map that kind onto the core token model, render native tokens without a token link or address line, and tag every bridged token row with its type. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Drop a fixture comment that restated the mock Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Rebaseline cross-chain token transfers screenshots after merging main Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix screenshot * Park the cursor before the flattened-source verification screenshot After the "Add item" click the cursor stayed over whichever "Library address" input the scroll offset put under it, so the dark-mode baseline carried a hover border that CI rendered on the other input. Move the mouse to the empty right margin and rebaseline. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Add spec for PostHog analytics integration (blockscout#3754) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Break PostHog analytics spec into tickets (blockscout#3754) Three agent-only vertical slices: extract the provider-neutral analytics facade, generalise the buffer queue to N providers, add the PostHog provider. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Extract provider-neutral analytics facade from the Mixpanel module Move the app's analytics API (logEvent, EventTypes, userProfile, reset, useInit, useLogPageView, getPageType) to src/shared/analytics and point every call site at it, so the Mixpanel module is reached only through the facade. Drop the unused userProfile.increment, rename the debug switch to _analytics_debug, and add a useRevoke spec for the touched revoke flow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Generalise the analytics buffer queue to multiple providers Move the buffer queue from the Mixpanel module into the analytics facade and fan it out to any number of providers behind one AnalyticsProvider interface. Each enabled provider's SDK is initialised once, set up (super-props, identity, profile writes), then the buffer is replayed into it with the original timestamp; a provider whose init or setup throws is dropped on its own. The Mixpanel module shrinks to a thin provider, the enabled-provider list lives in the facade's provider registry, and an ESLint rule keeps call sites off vendor modules. The send_immediately option becomes the provider-neutral sendImmediately track option. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add PostHog as a second analytics provider PostHog is enabled by NEXT_PUBLIC_POSTHOG_API_KEY (with optional NEXT_PUBLIC_POSTHOG_CONFIG_OVERRIDES), gated on private mode and the Usercentrics "PostHog" consent, and receives the same events, identity and person properties as Mixpanel through the shared analytics queue. Page views map to the native $pageview event; replayed events keep their original timestamp. The CSP admits *.posthog.com only when the provider is enabled. posthog-js is pinned and loaded as a lazy chunk. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Prune PostHog analytics task working files Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Address review nits on PostHog analytics PR Mark NEXT_PUBLIC_POSTHOG_API_KEY as Required, rename leftover Mixpanel-named analytics bindings, and declare useInit's return type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test the analytics queue against real provider registry; restore PickByType Drop the ./providers mock from the queue spec and enable providers via withEnvs instead. Restore the PickByType utility type removed with its last call site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Exclude posthog-js from the license check Its dual MIT/Apache-2.0 license expression is not recognised by license-report-check, same as @helia/verified-fetch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Close mutation-testing gaps in PostHog provider and analytics queue Cover sending a live event immediately without a timestamp, and suppress the equivalent mutant on the queue's track-only timestamp narrowing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…scout#3765) * Fix code editor crash when contract sources repeat a file path Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Address review: declare return types and strengthen editor-data tests Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…eme (blockscout#3763) Fix the white flash on reload in Firefox for dark color themes Render the color mode script in <head> so the theme class is set on <html> before the first paint, instead of only after the global styles in <body>. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…coin (blockscout#3762) Fix missing decimals in the withdrawals counter sum Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…blockscout#3769) Fix search on the bridged tokens tab with cross-chain txs enabled The interchain-indexer bridged_tokens resource did not declare q in its filterFields, so since the paginated list moved to URL-derived filters the search term was pushed to the URL but never sent to the API. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
If your organization's extra usage balance is empty, an organization admin can add extra usage credits at claude.ai/admin-settings/usage. If its monthly spend limit was reached, an admin can raise it on the same page. If neither applies, contact Anthropic support.
Once extra usage is available, someone with write access to this repository can comment @claude review on this pull request to trigger a review.
WalkthroughWarning Review details and warnings were omitted to fit the comment limit. |
There was a problem hiding this comment.
Code Review
This pull request introduces extensive updates across the repository, including upgrading Node.js to v24.21.0, integrating PostHog analytics under a unified facade, adding ERC-8056 token multiplier support, refactoring paginated query hooks, and replacing mobile list views with horizontally scrollable tables. It also rewrites several tools in TypeScript, optimizes Playwright test selection in CI, and introduces cognitive-complexity, mutation-testing, and Jev-review-screen gates. The review feedback is highly critical as it identifies several unresolved merge conflicts in package.json, .gitignore, and docs/ENVS.md that must be resolved immediately to prevent build and dependency installation failures.
| <<<<<<< HEAD | ||
| "@blockscout/api-types": "0.0.1-beta.d261ddc", | ||
| ======= | ||
| "@blockscout/api-types": "0.0.1-beta.40c5c457b4", | ||
| >>>>>>> v2.12.0 |
There was a problem hiding this comment.
Unresolved merge conflict markers found in package.json. Since this is a JSON file, these markers make the file invalid JSON, which will break pnpm install and any build step immediately. Please resolve the conflict by choosing the correct version (typically the upstream version 0.0.1-beta.40c5c457b4 when syncing upstream).
"@blockscout/api-types": "0.0.1-beta.40c5c457b4",| <<<<<<< HEAD | ||
| .codex/worktrees/ | ||
| ======= | ||
| /ds-bundle | ||
| >>>>>>> v2.12.0 |
| <<<<<<< HEAD | ||
| | NEXT_PUBLIC_COLOR_THEMES | `Array<'light' \| 'dim' \| 'midnight' \| 'dark'>` | Color themes the app offers in the settings menu. | - | all themes | `['light', 'dark']` | upcoming | | ||
| ======= | ||
| | NEXT_PUBLIC_COLOR_THEMES | `Array<'light' \| 'dim' \| 'midnight' \| 'dark'>` | Color themes the app offers in the settings menu. | - | all themes | `['light', 'dark']` | v2.11.2+ | | ||
| >>>>>>> v2.12.0 |
There was a problem hiding this comment.
Unresolved merge conflict markers found in docs/ENVS.md. Please resolve the conflict by keeping the upstream version v2.11.2+.
| <<<<<<< HEAD | |
| | NEXT_PUBLIC_COLOR_THEMES | `Array<'light' \| 'dim' \| 'midnight' \| 'dark'>` | Color themes the app offers in the settings menu. | - | all themes | `['light', 'dark']` | upcoming | | |
| ======= | |
| | NEXT_PUBLIC_COLOR_THEMES | `Array<'light' \| 'dim' \| 'midnight' \| 'dark'>` | Color themes the app offers in the settings menu. | - | all themes | `['light', 'dark']` | v2.11.2+ | | |
| >>>>>>> v2.12.0 | |
| | NEXT_PUBLIC_COLOR_THEMES | `Array<'light' \| 'dim' \| 'midnight' \| 'dark'>` | Color themes the app offers in the settings menu. | - | all themes | `['light', 'dark']` | v2.11.2+ | |
| | --- | --- | --- | --- | --- | --- | --- | | ||
| | NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS | `[{ text: string; icon?: string; dappId?: string, url?: string, isEssentialDapp?: boolean }]` | An array of dropdown items containing the button text, icon name and dappId in Dappscout or an external url | - | - | `[{'text':'Swap','icon':'swap','dappId':'swap','isEssentialDapp':true},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'}]` | v1.31.0+ | | ||
| <<<<<<< HEAD | ||
| | NEXT_PUBLIC_DEFI_DROPDOWN_BUTTON_TEXT | `{ desktop: string; mobile?: string }` | Text on the button that opens the dropdown. `mobile` is the shorter form shown on narrow screens. Keep `desktop` within ~24 characters and `mobile` within ~10. | - | `{'desktop':'Blockscout DeFi','mobile':'DeFi'}` | `{'desktop':'MyChain DeFi','mobile':'DeFi'}` | upcoming | | ||
| ======= |
There was a problem hiding this comment.
Unresolved merge conflict markers found in docs/ENVS.md. Please resolve the conflict by keeping the upstream version v2.11.2+.
| | --- | --- | --- | --- | --- | --- | --- | | |
| | NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS | `[{ text: string; icon?: string; dappId?: string, url?: string, isEssentialDapp?: boolean }]` | An array of dropdown items containing the button text, icon name and dappId in Dappscout or an external url | - | - | `[{'text':'Swap','icon':'swap','dappId':'swap','isEssentialDapp':true},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'}]` | v1.31.0+ | | |
| <<<<<<< HEAD | |
| | NEXT_PUBLIC_DEFI_DROPDOWN_BUTTON_TEXT | `{ desktop: string; mobile?: string }` | Text on the button that opens the dropdown. `mobile` is the shorter form shown on narrow screens. Keep `desktop` within ~24 characters and `mobile` within ~10. | - | `{'desktop':'Blockscout DeFi','mobile':'DeFi'}` | `{'desktop':'MyChain DeFi','mobile':'DeFi'}` | upcoming | | |
| ======= | |
| | NEXT_PUBLIC_DEFI_DROPDOWN_BUTTON_TEXT | `{ desktop: string; mobile?: string }` | Text on the button that opens the dropdown. `mobile` is the shorter form shown on narrow screens. Keep `desktop` within ~24 characters and `mobile` within ~10. | - | `{'desktop':'Blockscout DeFi','mobile':'DeFi'}` | `{'desktop':'MyChain DeFi','mobile':'DeFi'}` | v2.11.2+ | |
There was a problem hiding this comment.
Code Review
This pull request introduces several major features and tooling upgrades, including updating the Node.js and pnpm versions, integrating PostHog analytics via a provider-neutral facade, scoping interchain indexer requests, adding ERC-8056 token multiplier support, refactoring paginated query hooks, and replacing remaining mobile list views with scrollable tables. It also adds cognitive-complexity, CRAP-score, and StrykerJS mutation testing CI gates, and rewrites tooling scripts in TypeScript. The review feedback highlights a TypeScript compilation error in AdvancedFilter.tsx due to an un-narrowed null check on a ref, and suggests using loose inequality checks (!= null) in Epoch.tsx and AddressFavoriteButton.tsx to safely guard against both null and undefined values.
| const handleFilterChange = React.useCallback(<T extends keyof AdvancedFilterParams>(field: T, val: AdvancedFilterParams[T]) => { | ||
| if (pendingFilters.current === null) { | ||
| pendingFilters.current = { ...latestFilters.current }; | ||
| queueMicrotask(() => { | ||
| const nextFilters = pendingFilters.current ?? {}; | ||
| pendingFilters.current = null; | ||
| onFilterChange(nextFilters.age ? omit(nextFilters, AGE_RANGE_FIELDS) : nextFilters); | ||
| }); | ||
| } | ||
| pendingFilters.current[field] = val; | ||
| }, [ onFilterChange ]); |
There was a problem hiding this comment.
Since 'pendingFilters.current' is typed as 'AdvancedFilterParams | null', TypeScript will raise a compilation error ('Object is possibly null') when attempting to assign a value to 'pendingFilters.current[field]' directly. Using a local variable to hold and narrow the reference resolves this compilation issue safely.
const handleFilterChange = React.useCallback(<T extends keyof AdvancedFilterParams>(field: T, val: AdvancedFilterParams[T]) => {\n let current = pendingFilters.current;\n if (current === null) {\n current = { ...latestFilters.current };\n pendingFilters.current = current;\n queueMicrotask(() => {\n const nextFilters = pendingFilters.current ?? {};\n pendingFilters.current = null;\n onFilterChange(nextFilters.age ? omit(nextFilters, AGE_RANGE_FIELDS) : nextFilters);\n });\n }\n current[field] = val;\n }, [ onFilterChange ]);
| { ...truncationProps } | ||
| /> | ||
| { epochQuery.data.end_block_number && ( | ||
| { epochQuery.data.end_block_number !== null && ( |
There was a problem hiding this comment.
Using strict inequality '!== null' does not guard against 'undefined' values. If 'end_block_number' is missing or undefined, this check will evaluate to 'true' and attempt to render '' with an undefined number. Using loose inequality '!= null' is safer as it checks for both 'null' and 'undefined'.
| { epochQuery.data.end_block_number !== null && ( | |
| { epochQuery.data.end_block_number != null && ( |
| showEmailAlert | ||
| /> | ||
| { formData.id && ( | ||
| { formData.id !== undefined && ( |
There was a problem hiding this comment.
Using strict inequality '!== undefined' does not guard against 'null' values. If 'formData.id' is 'null', the condition will evaluate to 'true' and render the delete modal with an invalid ID. Using loose inequality '!= null' is safer as it handles both 'null' and 'undefined'.
| { formData.id !== undefined && ( | |
| { formData.id != null && ( |
9fcf3ce to
4bcf1a6
Compare
There was a problem hiding this comment.
If your organization's extra usage balance is empty, an organization admin can add extra usage credits at claude.ai/admin-settings/usage. If its monthly spend limit was reached, an admin can raise it on the same page. If neither applies, contact Anthropic support.
Once extra usage is available, someone with write access to this repository can comment @claude review on this pull request to trigger a review.
4bcf1a6 to
0a5c0a6
Compare
0a5c0a6 to
5c861b2
Compare
Upstream Sync - v2.12.0
Auto-merge with upstream
v2.12.0failed due to conflicts.To resolve:
v2.12.0to trigger Docker buildUpstream release notes