Skip to content

chore(lint): anti-slop rungs 4+5 — enable no-unknown-returns and no-chained-type-assertions, fix 73+126 violations - #1329

Merged
radandevist merged 7 commits into
developfrom
lane/wt-rung
Aug 25, 2026
Merged

chore(lint): anti-slop rungs 4+5 — enable no-unknown-returns and no-chained-type-assertions, fix 73+126 violations#1329
radandevist merged 7 commits into
developfrom
lane/wt-rung

Conversation

@radandevist

@radandevist radandevist commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Part of #1160
Closes #1330

Summary

Lands two rungs of the anti-slop ladder in one PR (precedent: rung 2 shipped five rules at once):

  • Rung 4anti-slop/no-unknown-returns: enabled at error, all 73 baseline violation sites fixed by naming return types instead of leaking unknown.
  • Rung 5anti-slop/no-chained-type-assertions: enabled at error, all 126 baseline violation sites (measured 2026-08-24, rule-only config-copy method at base 759aa0109) replaced with canonical patterns.

Why one PR: while rebasing onto fresh develop we found rung 4 had not been merged after all (it lives only on this branch), and the rung-4 fixes are a strict subset of this branch's work — splitting would mean unwinding shared helper extractions for no review benefit.

No suppressions anywhere; every site is genuinely fixed rather than hidden. Also clears the fresh hits that appeared when develop added tenant-users code after our baseline was measured (see last bullet).

CI fix (rebase onto origin/develop)

Model for this fix pass: Ox Alpha via Nous Portal (max effort), driven by jcode.

Rebased onto origin/develop (it gained #1314 moving the staff-user edit route, the #1310 publy/no-iife rule, and #1315). Eight lint errors remained after the rebase; every one is fixed the same way as the rest of this PR — real narrowing or named types, zero suppressions, no allowlist, no rule downgrade:

  • $userId-edit.blocker.test.tsx (fix(1301): move ref access out of render in staff user edit route #1314 moved the edit route; 7 hits): adopts this branch's canonical widening helpers — widenOptions<T> (one assert through a named shape) plus an addChildrenOf accessor, matching the sibling real-route suites; the history.block capture shim is typed straight off its bound method (Parameters<typeof originalBlock>[0][]), so it needs no assertion at all; QueryState.refetch returns Promise<void> like the query surface it stubs instead of Promise<unknown>.
  • publy/no-iife.ts (lint: add custom publy/no-iife (port from DigitalPrevention) and extract existing IIFEs #1310 landed after our baseline; 1 hit): the brand-new rule carried its own chained assertion inside unwrapCallee; replaced by a single intersection assert (ESTree.Expression & { expression: ESTree.Expression }) naming the member oxlint's ESTree typings omit.
  • anti-slop/shared/lexical-type-parameters.ts: the visitor-key record cast stopped compiling against the refreshed @oxlint/plugins typings; swapped for Reflect.get(node, key) — no cast, and the existing isNode guard narrows the result.

Baseline

Rule Violations Files
anti-slop/no-unknown-returns 73 46
anti-slop/no-chained-type-assertions 126 75

Canonical replacements (representative)

  • Route-object widening in route tests (__root-error-boundary.test.tsx, section-routing.test.tsx, $profileId-edit.test.tsx, layout.test.tsx, tab-routing.test.tsx, …): the TanStack route object exposes .addChildren/beforeLoad/component at runtime but its exported type doesn't name them — each site widens once through a local widenOptions<T> helper instead of as unknown as {…} chains.
  • Faithful route mocks (41 factories across route tests): tests read members off Route.options, but most mocked createFileRoute with a factory returning the options object itself, leaving .options undefined at runtime. Every lying factory now returns { ...options, options }, matching the real route object's shape (including where factories attach hook stubs).
  • Named return types (rung 4, front + shared-ts production sources): functions that returned bare unknown now return the domain type their callers already expect.
  • Fake API errors (router.test.ts): {…} as unknown as ErrorObject.assign(new Error('Unauthorized'), { responseStatusCode: 401, … }), matching the existing fake-500 pattern.
  • Vendor CSS props in contrast specs (toast-contrast.spec.ts, ×2 sites): style as Record<string, string | undefined> → typed getPropertyValue('-webkit-background-clip') / getPropertyValue('mask-image') access.
  • ts-morph narrowing guards (drawer-description-contrast.test.ts, drawer-form.test.tsx): kind-compare + anonymous-shape casts → Node.isFunctionDeclaration(…)-family guards, with a FunctionLikeNode union type predicate so parameter walks stay assertion-free.
  • matchMedia stub (vitest.setup.ts): removed the as MediaQueryList cast entirely — the stub returns a structural subset widened once via as typeof window.matchMedia; deprecated addListener/removeListener deleted (verified unused repo-wide).
  • Production sources (run-oxlint.ts, iso-analytics.ts, lexical-type-parameters.ts, invitations/index.tsx, profiles.tsx, users.tsx, staff-tenant-users.ts): double casts collapsed to single asserts or removed where inference already suffices; createStaffTenantUserInvitationMutationOptions is now exported and asserted directly under the mocked identity useMutation.
  • Kiota untyped bodies (staff-global-tenant-users.ts, code added on develop mid-flight): inline createUntypedObject(…) as unknown as Body → extracted buildAssignCompaniesBody(variables): AssignTenantUserCompaniesForStaffBody naming the wire type once.

Proof

Throwaway const x = {} as unknown as string; at repo root → pnpm lint fires:

proof-cta-violation.ts:1:11: error anti-slop(no-chained-type-assertions): This assertion chain discards type evidence ...

Remove → green. Same probe pattern fires anti-slop(no-unknown-returns) for function f() { return {} as unknown; }.

Gates

Gate Result
pnpm lint (repo-wide oxlint) ✅ 0 errors post-rebase
pnpm format (oxfmt check)
just ci-drift
pnpm --filter front typecheck
pnpm --filter shared-ts typecheck
pnpm --filter lint-ts typecheck
pnpm --filter shared-ts test ✅ (82/82)
pnpm --filter lint-ts test ✅ (676/676)
pnpm --filter front test ✅ (2393/2393, 220 files)

Model

Ox Alpha via Nous Portal (max effort), driven by jcode

Part of #1160 — rung 4 fixes, enabling `anti-slop/no-unknown-returns`.

packages/shared-ts:
  - any.utils: drop local AsyncFunction for GenericFunction; DeepReadonly
    branches on GenericFunction
  - try-catch: Handler/AsyncHandler/ErrorHandler resolve to void instead of
    unknown (error handlers are consumed, never returned)

front lib:
  - breadcrumbs: new EntityCrumbPayload union of the seven entity-crumb DTOs;
    EntityCrumbQuery.queryFn returns it instead of unknown. Adding an entity
    crumb now means adding its DTO to the union, on purpose.
  - locale-switch, session-validation: navigator/session helpers return
    named shapes instead of unknown
  - staff/$invitationId: InvitationDetailsError.refetch is typed as the real
    QueryObserverResult promise; onRefresh awaits it explicitly so the prop
    stays () => Promise<void>

tests (~40 files):
  - server-fn mocks keep the handler's return type via a generic wrapper
    (a void-typed wrapper silently dropped promises and broke 13 tests)
  - router/client-manager/query mocks use vi.fn<Parameters, ReturnType>
    generics and named fake types instead of bare vi.fn()
  - unwrapUntyped gets a recursive Unwrapped alias; the terminal return
    carries a documented cast (the recursion exhausts unknown by construction)
  - breadcrumb-contract: the fake Kiota client is a self-referencing
    FakeApiClient interface (callable + chainable index signature); respond
    accepts FakeResponse | Promise<FakeResponse>

e2e: parity-happy-path/staff-profiles helpers return typed row/summary shapes.

Verification: repo-config oxlint with no-unknown-returns:error → 0 findings;
pnpm --filter front typecheck clean; front suite 2313 passed; shared-ts 82
passed; just ci-front green; just react-doctor clean.

Refs #1160
Part of #1160 — rung 5 fixes, enabling `anti-slop/no-chained-type-assertions`.

All ~126 chained assertion sites are rewritten to canonical patterns:
single widening asserts through named helpers, typed narrowing predicates
(ts-morph Node.is* guards), Object.assign(new Error(...)) for fake API
errors, and typed getPropertyValue access for vendor CSS props. No
suppressions; every chain is gone rather than hidden.
Part of #1160 — follow-up to the rung 5 CTA rewrite.

The scripted CTA rewrite moved test reads from `Route.component` to
`Route.options.component`, which is correct against the real
@tanstack/react-router (members live under `.options`), but most route
tests mock `createFileRoute` with a factory that returns the options
object itself, leaving `.options` undefined at runtime. Every lying
factory now returns `{ ...options, options }` — faithful to the real
route object's shape, including where factories attach hook stubs.
Full front lane green: 2335 tests.
The rebased rung flips made lint catch two fresh files that landed on
develop after the baseline was measured:

- staff-global-tenant-users.ts: inline createUntypedObject widened with a
  chained assertion before post(); extracted into buildAssignCompaniesBody
  naming AssignTenantUserCompaniesForStaffBody once.
- tab-routing.test.tsx: three chained casts replaced with the canonical
  single-shape helpers already used by section-routing.test.tsx
  (widenOptions/addChildrenOf/routerPathnameOf).
- $userId-edit.blocker.test.tsx (#1314 moved the edit route): adopt the
  branch's canonical widening helpers (widenOptions/addChildrenOf) and type
  the history.block shim straight off the bound method so it needs no
  assertion at all; QueryState.refetch returns Promise<void> like the real
  hook surface it stubs.
- publy/no-iife (#1310) carried its own chained assertion inside
  unwrapCallee; one intersection assert names the missing ESTree member.
- anti-slop/shared/lexical-type-parameters: the visitor-key record cast no
  longer compiles against the updated @oxlint/plugins typings; Reflect.get
  needs no cast and isNode narrows the result.
@radandevist
radandevist merged commit 301f2b8 into develop Aug 25, 2026
28 of 29 checks passed
@radandevist
radandevist deleted the lane/wt-rung branch August 25, 2026 00:28
@radandevist

Copy link
Copy Markdown
Collaborator Author

Round-1 follow-up filed as #1337 (single as-never casts).

radandevist added a commit that referenced this pull request Aug 25, 2026
 follow-up)

Bring develop's #1329/#1332 tightening into the modules this lane split:
- enable anti-slop/no-chained-type-assertions and no-unknown-returns (error)
- retype membership/staff-user mutation contracts to named domain results
  instead of Promise<unknown>
- replace chained `as unknown as` with single named widening points
  (widenFake/componentOf) or full typed literals
- matchMedia mock returns a real MediaQueryList without an assertion chain
- port no-iife conditional/logical callee peeling (#1327) and the real-route
  Trans render guard coverage (#1312)
- check-ci-gate-structure: cover the new gate steps
radandevist added a commit that referenced this pull request Aug 25, 2026
…1264 review r1)

Rebasing onto origin/develop picks up #1329 (anti-slop rungs 4+5),
which enables anti-slop/no-chained-type-assertions at error. The merge
ref CI runs against flags four chained-assertion sites in files this PR
touches: use-language-keyed-zod-resolver.ts:40 (new in this PR) and the
three ROUTE_COMPONENTS entries of trans-render.guard.test.tsx
(pre-existing on develop, whose own quality gate is red for them since
the #1329 push).

Fixed with real narrowing, per that commit's own recipe — no
suppressions, no config change:
- resolver hook: single-cast seam (zodResolver(...) as Resolver<T>),
  dropping the as-unknown-as chain;
- test: routeComponentOf() helper with one assertion plus a runtime
  function guard, replacing the three widened object literals.

Validation: pnpm lint exit 0 (rule active), front typecheck exit 0,
oxfmt clean, just react-doctor exit 0, just ci-drift exit 0, targeted
trans-render suite + production build + artifact guard (97 >= 72) +
full front suite under the heavy lock, all green.
radandevist added a commit that referenced this pull request Aug 25, 2026
Closes #819.

Staff profiles were read-only after creation: no UI reached the existing `PATCH /staff/profiles/{profileId}` contract. This adds an edit-details drawer on `/staff/profiles/$profileId` mirroring the tenant profile edit drawer: name, description, icon+tone (shared `IconColorPicker`, live tile preview), `?edit=1` deep link, `useBlocker` unsaved-draft guard, 422 `fieldErrors` mapped by stable key with unmapped/empty payloads surfaced as a root banner, cache invalidation, en+fr i18n. Tests: drawer component tests, real-router edit-flow routing test, registration in the drawer-form geometry guard, the browser scroll-geometry spec and the drawer-description contrast inventory. No API contract change.

Also repairs develop's red "quality gate" (the #1312 × #1329 `no-chained-type-assertions` collision in the trans-render guard test) via a runtime boundary guard, no suppressions.

Implementer: Ox Alpha (stealth/ox-alpha via Nous Portal, max effort, jcode). Reviewer: tencent/hy3:free (Nous Portal, high) — APPROVED_WITH_FOLLOW_UPS at 1d112a6; CI fully green at that tip (front-e2e 4/4). Follow-ups filed: pristine Save still sends a no-op PATCH; empty `errors: {}` 422 path untested. Unverified: browser e2e only via CI.
radandevist added a commit that referenced this pull request Aug 25, 2026
…1264 review r1)

Rebasing onto origin/develop picks up #1329 (anti-slop rungs 4+5,
enabling anti-slop/no-chained-type-assertions at error) and #1335.
The merge-ref CI flagged four chained-assertion sites in files this PR
touches: use-language-keyed-zod-resolver.ts:40 (introduced by this PR)
and three ROUTE_COMPONENTS entries in trans-render.guard.test.tsx
(pre-existing on develop).

Resolution, no suppressions and no config change:
- resolver hook: single-cast seam (zodResolver(...) as Resolver<T>),
  dropping the as-unknown-as chain;
- guard test: develop itself fixed the three entries in #1335 with its
  routeComponentThunk helper; the rebase resolves that file to
  develop's version verbatim instead of introducing a second,
  competing helper.

Validation: pnpm lint exit 0 (rule active), front typecheck exit 0,
oxfmt clean, targeted trans-render suite green, just react-doctor
exit 0, just ci-drift exit 0.
radandevist added a commit that referenced this pull request Aug 25, 2026
…1264 review r1)

Rebasing onto origin/develop picks up #1329 (anti-slop rungs 4+5,
enabling anti-slop/no-chained-type-assertions at error) and #1335.
The merge-ref CI flagged four chained-assertion sites in files this PR
touches: use-language-keyed-zod-resolver.ts:40 (introduced by this PR)
and three ROUTE_COMPONENTS entries in trans-render.guard.test.tsx
(pre-existing on develop).

Resolution, no suppressions and no config change:
- resolver hook: single-cast seam (zodResolver(...) as Resolver<T>),
  dropping the as-unknown-as chain;
- guard test: develop itself fixed the three entries in #1335 with its
  routeComponentThunk helper; the rebase resolves that file to
  develop's version verbatim instead of introducing a second,
  competing helper.

Validation: pnpm lint exit 0 (rule active), front typecheck exit 0,
oxfmt clean, targeted trans-render suite green, just react-doctor
exit 0, just ci-drift exit 0.
radandevist added a commit that referenced this pull request Aug 25, 2026
…1264 review r1)

Rebasing onto origin/develop picks up #1329 (anti-slop rungs 4+5,
enabling anti-slop/no-chained-type-assertions at error) and #1335.
The merge-ref CI flagged four chained-assertion sites in files this PR
touches: use-language-keyed-zod-resolver.ts:40 (introduced by this PR)
and three ROUTE_COMPONENTS entries in trans-render.guard.test.tsx
(pre-existing on develop).

Resolution, no suppressions and no config change:
- resolver hook: single-cast seam (zodResolver(...) as Resolver<T>),
  dropping the as-unknown-as chain;
- guard test: develop itself fixed the three entries in #1335 with its
  routeComponentThunk helper; the rebase resolves that file to
  develop's version verbatim instead of introducing a second,
  competing helper.

Validation: pnpm lint exit 0 (rule active), front typecheck exit 0,
oxfmt clean, targeted trans-render suite green, just react-doctor
exit 0, just ci-drift exit 0.
radandevist added a commit that referenced this pull request Aug 25, 2026
…1264 review r1)

Rebasing onto origin/develop picks up #1329 (anti-slop rungs 4+5,
enabling anti-slop/no-chained-type-assertions at error) and #1335.
The merge-ref CI flagged four chained-assertion sites in files this PR
touches: use-language-keyed-zod-resolver.ts:40 (introduced by this PR)
and three ROUTE_COMPONENTS entries in trans-render.guard.test.tsx
(pre-existing on develop).

Resolution, no suppressions and no config change:
- resolver hook: single-cast seam (zodResolver(...) as Resolver<T>),
  dropping the as-unknown-as chain;
- guard test: develop itself fixed the three entries in #1335 with its
  routeComponentThunk helper; the rebase resolves that file to
  develop's version verbatim instead of introducing a second,
  competing helper.

Validation: pnpm lint exit 0 (rule active), front typecheck exit 0,
oxfmt clean, targeted trans-render suite green, just react-doctor
exit 0, just ci-drift exit 0.
radandevist added a commit that referenced this pull request Aug 25, 2026
# react-doctor rung 1: re-enable `no-giant-component`

Part of #1291 · Closes #1313

Model: Ox Alpha (stealth/ox-alpha via Nous Portal, max effort, jcode)

## What

`apps/front/doctor.config.json` has carried three global rule kill-switches since #1247. This rung removes **exactly one line** — `"react-doctor/no-giant-component": "off"` — re-enabling the rule tree-wide at its default severity. No other config change (verified minimal diff).

## Why the config change is justified (per the guard-config review rule)

The rule was turned off globally as a shortcut in #1247 because the `--scope files` gate inherits pre-existing debt in every touched file. Every offender is now split below the 300-line budget, so flipping the rule back on produces **zero findings tree-wide**: nothing pre-existing gets inherited by later PRs, which is what made the kill-switch necessary in the first place.

## Before / after

| Measurement | Count |
|---|---|
| `no-giant-component` findings, rung-0 baseline (2026-08-24, react-doctor@0.9.12, full tree, default severity) | **16 / 16 files** |
| Findings after this branch | **0** |
| Local CI replica: `--scope files --base origin/develop --blocking warning --verbose` | green |

## Proof the rule actually runs

Before the flip, a 315-line canary (`src/routes/authed/staff/_giant-canary.tsx`) was planted on the tree: the pinned 0.9.12 scanner flagged it immediately (`no-giant-component`, components must stay ≤300 lines). The canary was removed right after; nothing of it remains in this branch.

## The splits (11 commits, rebased onto current develop)

- `data-table.tsx` → toolbar/pagination/empty-state parts
- staff details trio (`profiles/$profileId`, `staff-users/$userId`, tenant `users/$userId`) → shared detail shells/views/lifecycle helpers
- `tenants-new.tsx` / `$tenantId-edit.tsx` / `$tenantId.tsx` → form sections, stat rows, danger zone
- `invitations.tsx` → route-search module (+test), filter-state, table wrapper, drawer hosts; **re-split after #1306 rewrote the page wholesale** (447 → 286 lines), dropping `_invitation-status-label.ts` as a byte-identical duplicate of the helper `_invitation-columns.tsx` already exports
- `profiles.tsx` / `profiles/$profileId` tab pages → permission matrix, members card, identity header
- `_assign-members-drawer.tsx`, `_profile-permissions-tab.tsx` → selection/list parts
- `tenant/settings/general.tsx` → form field groups

During conflict resolution, develop's parallel refactors (#1306, #1295, #1292) were preferred wherever they superseded ours; our extractions were re-applied only where develop's version still exceeded the budget. Bulk-action logic in `tenants/$tenantId/users.tsx` verified identical to develop's inline version before extraction.

## Round 1 fixes — what failed in e2e and why (d315142)

An earlier DONE claim on `db5f815e2` was wrong: `front-e2e` shards 2–4 were RED with five failing specs:

| Spec | Symptom |
|---|---|
| `parity-happy-path.spec.ts:221` | after clearing the staff-users search, only the filtered row remained; URL no longer had `q=` |
| `staff-profiles.spec.ts:122` | same symptom — "Staff Owner" row not restored after clear |
| `staff-tenants.spec.ts:201` | resetting the status filter did not restore "Acme Corporation" |
| `staff-tenant-details.spec.ts:867` | level filter expected `disabled` during row selection, received `enabled` |
| `table.spec.ts:150` | pager "Previous" did not return to page 1's row |

**Root cause — one class for all five:** the DataTable split extracted grid/header children that read the TanStack table instance during render. That instance is mutable and identity-stable across renders, so under the React Compiler (active in production builds via `viteReact({ compiler: true })`, which is how e2e serves the app) `DataTable`'s JSX children were memoized: a parent-only re-render no longer re-executed their bodies. After a search clear, filter reset, or pagination move the page state and toolbar were fresh while the rows stayed frozen on the previous query's data. Plain vitest never reproduces this because unit transforms do not run the compiler pass — the repro required a compiler-enabled vitest config over the real components (fails exactly like CI without the directive, passes 43/43 with it).

**Fix:** `'use no memo'` on `DataTable`, `DataTableGrid` and `DataTableHeaderRow`, each with a justification comment pointing at the full rationale in `data-table.tsx`. No test edits, no skips/retries/timeout changes; every component stays ≤300 lines.

## Resume/rebase (2026-08-25, merge commit 1ab3f51)

The previous session died on a provider error mid-flight, leaving ~110 files staged from an **unfinished `git merge origin/develop`**. Resumed from disk instead of restarting:

- Re-read every staged hunk: the staged tree was exactly the in-flight merge resolution porting develop's anti-slop rungs 4+5 (#1329), the `no-iife` conditional/logical callee peeling (#1332) and the real-route `<Trans>` render guard coverage (#1312) into this lane's split modules. Completed the merge commit `1ab3f5145` keeping BOTH parents (85e6037 × 301f2b8); the two loose files still in the worktree belonged to the same port and were folded in. Nothing half-finished was kept.
- `.oxlintrc.json`: **tightening only** — `anti-slop/no-chained-type-assertions` and `anti-slop/no-unknown-returns` go `off` → `error`. No rule disabled or loosened anywhere.
- The tightened rules surfaced 11 leftovers in modules only this lane has (split files with no develop counterpart). All fixed at root cause, no suppressions: mutation contracts retyped to named domain results (`GetStaffUserByIdResult`, the suspend/reactivate/remove result union) instead of `Promise<unknown>`; chained `as unknown as` casts replaced by single named widening points (`widenFake`, `componentOf`) or fully typed literals; the matchMedia test mock now builds a real `MediaQueryList` shape without an assertion chain.

Verification, all re-run on the merge result:

- `pnpm lint`: 0 errors with rungs 4+5 at error
- `pnpm --filter front typecheck`: green
- Full front unit/component suite: **235 files / 2530 tests green** (includes the design-system, z-index and React Compiler artifact guards)
- `just react-doctor`: no findings tree-wide, `no-giant-component` blocking
- `pnpm format`: clean; `just ci-drift`: green
- e2e intentionally not run locally (verification policy); CI runs front-e2e 4/4 on this PR and that is the acceptance evidence

## Verification

- `pnpm --filter front typecheck`: green
- Full front unit/component suite (`pnpm --filter front test` under `heavy.sh` serialization): green
- Full-tree react-doctor scan + local CI replica gate (`--scope files --base origin/develop --blocking warning`): green, exit 0
- Compiler-parity repro (temporary uncommitted vitest config with `compiler: true`): A/B proves the directive is load-bearing — 1 stale-row failure without it, 43/43 green with it
- e2e intentionally not run locally (verification policy); CI runs front-e2e 4/4 on this PR and that is the acceptance evidence
- develop moved repeatedly mid-flight (#1298, #1307, then #1305/#1306/#1310/#1314/#1328): merged forward each time. The last round did overlap (QueryDisplay contract, profiles dialog props, the `react/refs` override file, `no-iife`) and was absorbed by migrating our split pages to develop's patterns and re-porting develop's fixes into the split modules (`a36e04d4a`, `d9ed413ce`), keeping every component ≤300 lines
- 2026-08-25: merged forward again over #1312/#1329/#1331/#1332 (see Resume/rebase above); all gates re-run green on `1ab3f5145`

Two findings the scoped gate raised against our own extracted modules were fixed at root cause in this branch (no suppressions): the staff-user submit handler returned an explicit outcome instead of calling an injected `navigate()` outside a component, and `EditAccessSection`'s six boolean props collapsed into grouped query/pagination objects.


## Round 2 review fixes — suppression debt eliminated at root cause (2026-08-25)

Round-1 review required: rebase onto current develop, zero net-added disable directives vs origin/develop, root-cause fixes only. Three gaps were real and are fixed:

1. **Five lane-introduced `eslint-disable` lines.** The earlier splits had *relocated* suppressions from develop files into the new modules (2× `exhaustive-deps` from `data-table.tsx` → `use-matched-breakpoints.ts`, 1× `no-unused-vars` from `data-table.tsx` → `column-display-meta.ts`, 1× `exhaustive-deps` from `tenants-new.tsx` → `_tenants-new-form.tsx`, 1× from the old giant `users.tsx` → `_assign-members-state-hook.ts`, 1× from `_profile-permissions-tab.tsx` → `_profile-permissions-save-hook.ts`). All five removed without any suppression:
   - `use-matched-breakpoints.ts`: both callbacks re-derive the breakpoint array from `key` (`key.split(',').map(Number)`), so `[key]` is a complete dep array.
   - `_tenants-new-form.tsx`: resolver memo deps completed to `[t, i18n.language]`.
   - `_assign-members-state-hook.ts` / `_profile-permissions-save-hook.ts`: effects already guard on their own bookkeeping refs, so deriving the id/key sets from the stable key string inside the effect (`rowAccountIdsKey.split(',')`, `grantedSignature.split(',')`) makes the arrays complete with identical behavior.
2. **Five navigate-in-render disables** (from round 1): URL writers wrapped in `useCallback`, which is exactly the lexical contract the rule checks; directives deleted.
3. **Stale base**: rebased again onto `d26a1f17b` (#1358, lint-ts/docs only, no overlap), 26 commits replayed cleanly.

### Directive parity proof (A/B vs origin/develop, `git diff origin/develop...HEAD -- apps/front/src`)

| Direction | Lines matching `eslint-disable|react-doctor-disable` |
|---|---|
| Added | **0** |
| Removed | **9** |

### Full-tree react-doctor A/B (pinned 0.9.12, dedicated pristine-develop worktree `/tmp/wt-develop`, both under `heavy.sh`)

| Measurement | origin/develop | this branch |
|---|---|---|
| Files scanned | 655 | 749 |
| Score | 66 / 100 | 73 / 100 |
| Findings total | 48 | 41 |
| Finding families | 20 | 17 |
| `no-giant-component` | n/a (rule off in develop's config) | **0** (rule enabled by this PR) |

Every family count on the branch is ≤ develop's; six families that exist on develop (boolean-prop-combos, pure-function-per-render, loading-flag-outside-finally, prop-callback-in-effect ×2, second parent-sync family, one unused-file/export pair) are gone on the branch. Remaining 41 findings across 17 families are pre-existing develop debt in files this PR does not touch (e.g. `_invite-user-drawer.tsx`, `_change-email-dialog.tsx`, `app-shell.tsx`, `marketing-header.tsx`, `use-hydrated.ts`, `broadcast-sync.ts`, `confirm-dialog.tsx`, `login.tsx`, `floating-selection-bar.tsx`, `offset-pagination.ts`, `field-checkbox-group.tsx`) plus the known untouched `tenants-new-helpers.ts` iterations family and `_profile-edit-details-drawer.tsx` (#1335, exists identically on develop).

### Gates, all re-run at the pushed tip

- `just ci-front` under `heavy.sh`: **green** — 238 test files / 2563 tests, design-system/z-index/compiler-artifact guards included
- Local CI replica `pnpm dlx react-doctor@0.9.12 --scope files --base origin/develop --blocking warning` under `heavy.sh`: **Scanned 126 files · Score 100/100 · No issues found**, exit 0
- `pnpm --filter front typecheck`: green
- Targeted suites for every touched area during development: 168/168 (data-table, tenants-new, assign-members trio, permission matrix, profile details)
- e2e intentionally not run locally (verification policy); CI runs front-e2e 4/4 on this PR and that is the acceptance evidence


### Post-push CI status (honest transcript, 2026-08-25 ~06:00Z)

The `front e2e` **build job** fails on this PR's tip with an infrastructure error, not a diff error: after all four images build successfully, the step "Push stack images to GHCR" is denied with verbatim `denied: permission_denied: The requested installation does not exist.` — the runner's credential no longer has access to the personal namespace `ghcr.io/radandevist/publyapp-e2e`. A `--failed` re-run reproduced the identical denial. Two neighboring PRs hit the same step failure in the same window (05:32Z and 05:40Z), while one run at 05:34Z succeeded — the breakage started between 05:34Z and 05:46Z today and is shared across PRs. Nothing in this branch touches `.github/workflows` or image definitions; the fix belongs to the registry credential owner (restore the GitHub App/PAT installation for `ghcr.io/radandevist/publyapp-e2e`).

Everything else is green: `just ci-front` (238 files / 2563 tests) re-run locally under `heavy.sh` at the pushed tip, `just ci-drift` green, typecheck green, scoped react-doctor CI replica 100/100.


---
Implementer: Ox Alpha (Nous Portal, jcode, max effort). Reviews: r1 CHANGES_REQUIRED (suppression debt, false tree-wide claim), r2 APPROVED by hy3 (Go, last rung after the free chain) at ebbe48d: 0 suppressions added / 9 removed, react-doctor 0 no-giant-component tree-wide; tip 32d98c9 = ebbe48d + develop merge (update-branch after #1396); full CI green incl. front-e2e 4/4.
radandevist added a commit that referenced this pull request Aug 25, 2026
…1264 review r1)

Rebasing onto origin/develop picks up #1329 (anti-slop rungs 4+5,
enabling anti-slop/no-chained-type-assertions at error) and #1335.
The merge-ref CI flagged four chained-assertion sites in files this PR
touches: use-language-keyed-zod-resolver.ts:40 (introduced by this PR)
and three ROUTE_COMPONENTS entries in trans-render.guard.test.tsx
(pre-existing on develop).

Resolution, no suppressions and no config change:
- resolver hook: single-cast seam (zodResolver(...) as Resolver<T>),
  dropping the as-unknown-as chain;
- guard test: develop itself fixed the three entries in #1335 with its
  routeComponentThunk helper; the rebase resolves that file to
  develop's version verbatim instead of introducing a second,
  competing helper.

Validation: pnpm lint exit 0 (rule active), front typecheck exit 0,
oxfmt clean, targeted trans-render suite green, just react-doctor
exit 0, just ci-drift exit 0.
radandevist added a commit that referenced this pull request Aug 25, 2026
…1264 review r1)

Rebasing onto origin/develop picks up #1329 (anti-slop rungs 4+5,
enabling anti-slop/no-chained-type-assertions at error) and #1335.
The merge-ref CI flagged four chained-assertion sites in files this PR
touches: use-language-keyed-zod-resolver.ts:40 (introduced by this PR)
and three ROUTE_COMPONENTS entries in trans-render.guard.test.tsx
(pre-existing on develop).

Resolution, no suppressions and no config change:
- resolver hook: single-cast seam (zodResolver(...) as Resolver<T>),
  dropping the as-unknown-as chain;
- guard test: develop itself fixed the three entries in #1335 with its
  routeComponentThunk helper; the rebase resolves that file to
  develop's version verbatim instead of introducing a second,
  competing helper.

Validation: pnpm lint exit 0 (rule active), front typecheck exit 0,
oxfmt clean, targeted trans-render suite green, just react-doctor
exit 0, just ci-drift exit 0.
radandevist added a commit that referenced this pull request Aug 25, 2026
…ventory (#1264) (#1326)

Closes #1264

## Summary

Resolves every React Compiler compatibility follow-up queued in the #1234 skip inventory, then rebases onto a develop that advanced three times mid-flight (#1305, #1310, #1314, #1323/#1325 on the front side, plus API/scripts-only lanes). Regenerated against the final tree: **13 diagnostics across 9 files** (down from 36 across 20 when this branch started); **compiled client modules rise from 90 to 97** against the pinned floor of 72.

Per item (one commit each, red/green evidence under `.dump/` by name):

1. **`useWatch()` in `_create-post-drawer.tsx`** — replaces the render-time `watch('body')` read (IncompatibleLibrary).
2. **Shared `useLanguageKeyedZodResolver` hook** — replaces the `[i18n.language]`-memoised resolver + eslint-disable pattern in 7 files (Suppression family).
3. **`profiles.tsx` / `profiles-new.tsx`** — latest-callback ref indirection collapsed to plain functions; ref writes moved out of handlers into an open-transition effect (probe-verified: ref writes inside a handler passed to the column factory taint it); `lastEditedProfileRef` became state.
4. **Big forms render-time ref reads** — `invitations/new.tsx`: known-profile-names map → state with idempotent effect folds plus synchronous per-render union of fresh rows; redirect timer → state-armed deadline owned by an effect; saved-flag re-baseline via synchronous form reset before navigate. (`staff-users/$userId-edit.tsx`, originally covered here too, was later superseded by develop's #1314 — see Rebase record.)
5. **Preserve-memo drops** — `$tenantId/users.tsx` + `invitations.tsx`: manual useCallback/useMemo wrappers deleted; the compiler caches per value.

Plus an inventory refresh in `docs/guides/front/react-compiler.md` (every row dispositioned) and a gate commit clearing all react-doctor findings in touched files (HARD gate: findings must be fixed or suppressed with per-line rationale).

## Rebase record

Three rebases onto a moving origin/develop. Each resolution kept both intents where they coexisted and preferred develop's implementation where it already superseded ours:

1. **Rebase 1 — `_profile-form-drawer.tsx`:** resolved to develop's side entirely. Develop's rewrite moved form ownership to the host page and deleted the very effects/suppressions our commit targeted; our hunk had nothing left to preserve.
2. **Rebase 2 (+#1310 no-iife, #1314 refs-out-of-render, #1323/#1325 DataTable exemption) — `staff-users/$userId-edit.tsx`:** took develop's side everywhere. #1314 implements our item 4 better (module-level snapshots written outside render, adjust-state-during-render absorption, snapshot-based nav blocker), and it untaints the submit closure our version left flagged. Our parallel item-4 work in `invitations/new.tsx` survives unchanged.
3. **Rebase 3 (+#1315 uploads, #1328 launcher — API/scripts only, zero front overlap):** completed clean after discarding a locally regenerated `routeTree.gen.ts` Register block (local toolchain artifact; committed versions intentionally lack it).

Net effect on the skip inventory, recorded honestly in the refreshed doc:

- **Fixed elsewhere:** `$userId-edit.tsx` now compiles outright (#1314); `$tenantId-edit.tsx`'s Refs diagnostic stopped firing after #1305's QueryDisplay restructure; one `_assign-members-drawer` suppression went quiet; the erroneous `__root.tsx` row (its ref write was already effect-wrapped) was dropped.
- **Regressions owned:** #1305 reinstated try/finally in three handlers #1234 had fixed — `staff-users/$userId.tsx` (×2), tenant `users.tsx`, tenant `users/$userId.tsx`. They are re-queued in the inventory rather than papered over.
- **Omission corrected:** `_profile-edit-details-drawer.tsx`'s exhaustive-deps suppression predates this refresh but was missing from the earlier inventory; it is now listed as an acceptable skip.

## Gates

- Artifact guard on the final tree: **97 ≥ 72** compiled modules, runtime chunk present (`compiler-runtime-CNG2r3iR.js`), exit 0 (`check-react-compiler.mjs`)
- Full front suite under the heavy lock, post-rebase: **220 files / 2393 tests passed**, exit 0 (evidence tail `.dump/rebase2-final-suite.log`); typecheck clean
- Root lint gate (oxlint + disable-comment audit + frontend barrels) exit 0 · oxfmt check exit 0 · `just ci-drift` exit 0
- `react-doctor@0.9.12 --scope files --base origin/develop --blocking warning` exit 0
- Skip-inventory parser suite (`packages/scripts-ts`): 7/7 green
- Design-system guard (611 files, 0 violations) and z-index guard (16311 candidates) green inside the suite run

Implementer: Ox Alpha (stealth/ox-alpha via Nous Portal, max effort, jcode). Reviewer: pending adversarial review.

## Round 1 fix

Review r1 (APPROVED_WITH_FOLLOW_UPS) flagged the added `react-doctor-disable-next-line` comments as guard-loosening residue of the clearing commit. Disposition, per the repo rule that added suppression comments are themselves a finding:

- **Audited against origin/develop, not assumed.** A scratch worktree of origin/develop (@301f2b835) scanned with the pinned `react-doctor@0.9.12` shows the annotated effect patterns (open-transition resets, dirty-flag uplinks, click-handler navigate) ARE flagged on develop itself — genuinely pre-existing findings, not introduced by this diff.
- **Kept only under the sanctioned exemption**, each now carrying an explicit per-line rationale (deliberate pattern + pre-existing on develop); `profiles.tsx` restores develop's own rationale block verbatim. No code changes: the fix commit is a comment-only diff.
- **react-doctor before/after:** with the suppressions stripped entirely, `just react-doctor` (`--scope files --base origin/develop --blocking warning`) fails on those pre-existing findings surfaced in the touched files; with the rationales in place it exits 0 with zero new findings. Whole-repo score: develop 66 → this branch 68 (this PR's real fixes raise it; no regression).
- **Re-validated after the fix:** production build + artifact guard (97 ≥ 72 compiled modules, runtime chunk present), full front suite under the heavy lock (exit 0), front typecheck, root lint, oxfmt check, `just ci-drift` — all exit 0.

**Mid-flight develop advances handled:** the first fix push went red because origin/develop moved (#1329 anti-slop rungs 4+5 enabling `anti-slop/no-chained-type-assertions` at error, plus #1312/#1331/#1332); the merge-ref CI therefore flagged four chained-assertion sites in files this PR touches. Rebased onto origin/develop (clean, one overlapping file) and fixed them per #1329's own recipe — no suppressions, no config change: the resolver hook's `as unknown as` chain became a single cast seam, and the three `ROUTE_COMPONENTS` entries of `trans-render.guard.test.tsx` (pre-existing on develop) were narrowed via a local helper. That state went **fully green** in CI (`48a0d8c0a`: quality, react-doctor-gate, supply-chain, all four front-e2e shards). Develop then advanced again before merge: #1335 (staff profile edit surface) touched the same guard-test file with its own equivalent `routeComponentThunk` helper, and #1336 moved jobs code, leaving the PR CONFLICTING. Rebased once more; the sole conflict (`trans-render.guard.test.tsx`) was resolved to **develop's version verbatim** rather than keeping a second competing helper, so the top commit now reduces to the resolver-hook fix alone. That state went fully green locally and was pushed (`a7161537a`): CI came back green everywhere except one shard — `front-e2e (4/4)` died at 43 s inside the **Pull stack** step on a GHCR network timeout pulling the third-party `shopify/toxiproxy` image (`context deadline exceeded`), before a single test executed; the other three shards passed, so this was an infrastructure flake, not a diff regression. Meanwhile develop advanced yet again (#1343 adds the `publy/no-never-any-casts` rule, #1338 the NuGet-audit record — no file overlap with this PR). Final rebase onto that tip and full re-validation: root lint (both anti-slop rules active), front typecheck, oxfmt, `just react-doctor`, `just ci-drift` — all exit 0; targeted trans-render suite (27/27), production build with the artifact guard (97 ≥ 72 compiled modules), and the full front suite under the heavy lock — all green.

Round 1 fix implementer: Ox Alpha (via Nous Portal, max effort, jcode).

## Round 2 fix

Review r2 returned APPROVED_WITH_FOLLOW_UPS for exactly one reason: the round-1 fix had kept suppression comments where it could not immediately root-cause the finding. This round removes every added suppression by fixing the underlying pattern instead — zero suppression-type lines added vs origin/develop:

- **`_change-email-dialog.tsx`** — the open-transition reset effect is gone, replaced by a keyed wrapper: an outer shell increments `sessionKey` on every closed→open transition and remounts a pure `Inner` form through `key`. A fresh mount initialises from `defaultValues` by construction, so no imperative reset exists at all.
- **`_invite-user-drawer.tsx` / `_profile-edit-details-drawer.tsx`** — the dirty-flag uplink no longer compares watch-stream snapshots against a component-captured pristine baseline (the root of the `no-ref-initializer-runs-on-every-render`, `no-pass-live-state-to-parent` and `exhaustive-deps` findings). Dirtiness now comes from react-hook-form's own synchronous dirty computation (`control._getDirty`), which always answers "differs from the pristine session seed". The partial-failure reseed pins its baseline with `keepDefaultValues: true`, so retried failed rows still count as unsaved user data. The profile drawer keys sessions on `${sessionKey}:${profile.id}` so switching profiles remounts while a same-id refetch does not.
- **`use-language-keyed-zod-resolver.ts`** — doc comment reworded so it no longer contains the literal text `eslint-disable-next-line react-hooks/exhaustive-deps -- [i18n.language]` (grep-proof pollution only, no behaviour change).

**Suppression proof:** `git diff origin/develop --unified=0 | grep '^+' | grep -E 'react-doctor-disable|eslint-disable|@ts-expect-error'` returns **no matches** across the whole diff (transcript: `.dump/round2-suppression-proof.txt`; full-suite log tail: `.dump/round2-full-suite.log`). `react-doctor@0.9.12 --scope files --base origin/develop --blocking warning` exits 0 with **zero findings**.

**Gates after round 2:** front typecheck clean; production build + artifact guard **97 ≥ 72** compiled modules with runtime chunk present; full front suite under the heavy lock **224 files / 2436 tests passed**; three targeted suites **35/35**; root lint gate (oxlint + disable-comment audit + barrels) exit 0; oxfmt check exit 0; `just ci-drift` exit 0.

Round 2 implementer: ox-alpha (max effort, jcode).

## Unverified / blocked-by-infra

- All non-e2e checks are green on ad6116f (17 pass). CI-side front-e2e (4 specs) has not run locally by design; on CI it is currently **blocked by an owner-level GitHub Packages problem, not by this diff**: the `Push stack images to GHCR` step fails deterministically (`permission_denied: The requested installation does not exist`) before any test executes — reproduced across 5 runs (initial + 3 reruns + 1 fresh run), while image-content-neutral lanes succeeded in the same window with the same actor. The `publyapp-e2e-*` container packages resolve with `"repository": null` (unlinked from this repo), so token-authenticated uploads of new layers are refused. Full transcript: `.dump/round2-front-e2e-infra-failure.txt`; tracked in #1397 for the owner.
- API suites were not re-run: this branch touches no API code (front-only diff, verified via merge-base file list).



## Rebase after #1396

Develop merged #1396 (GHCR namespace + repo path repoint after the move to the PublyApp org) plus nine more commits (#1382/#1349, #1402, #1380, #1399/#1391 EF Core 10.0.11, #1351, #1358, #1355 specs, #1353), leaving this branch CONFLICTING again. Rebased onto `origin/develop` @ `f2811483a`; reviewed round-3 tip was `ad6116f97`, new tip is **`d7139e212`** (pushed with `--force-with-lease`). Exactly one conflict across the 14 rebased commits, in `docs/guides/front/react-compiler.md`: develop's side of the decision-vocabulary paragraph still pointed “follow-up” at #1264 as an open queue, while this lane's regenerated inventory (which auto-merged everywhere else in the same file) uses the post-#1264 vocabulary. Resolution keeps this lane's updated paragraph, verified against both sides in full before editing; no `--ours`/`--theirs` anywhere. #1396's workflow/package changes applied cleanly with no overlap. Lockfile untouched by develop, so no reinstall was needed. Full local re-validation on the new tip, all green: front typecheck exit 0; root lint gate (`just check-write`) exit 0; full front suite **224 files / 2263 tests passed** (exit 0); design-system guard 0 violations across 620 files; z-index guard OK; React Compiler artifact guard **97 ≥ 72** compiled modules with runtime chunk present. Full record: `.dump/rebase-report.md`.


## Rebase after #1396 again (#1385/#1381/#1360/#1403/#1318), tip 1b3abb1

Develop advanced again while the PR sat reviewed (#1385, #1381, #1360, #1403, #1318), leaving it CONFLICTING a third time. Rebased all 14 commits onto `origin/develop` @ `933319f3f`; reviewed tip `ad6116f97` → first-rebase tip `d7139e212` → **new tip `1b3abb19e`** (pushed with `--force-with-lease`). Five replay stops conflicted, all from develop's #1318 no-giant-component extraction of the giant staff forms; every resolution keeps both intents by taking develop's extracted structure and porting this PR's compiler work into its new home — no `--ours`/`--theirs` anywhere:

- **Item 2 (language-keyed resolver):** `tenants-new.tsx` keeps develop's thin wrapper; the resolver-hook change moved into `_tenants-new-form.tsx` (manual `useMemo(zodResolver(...), [t, i18n.language])` → `useLanguageKeyedZodResolver`, max-seats ref-getter preserved). `$tenantId-edit.tsx` and `$tenantId/users/$userId-edit.tsx` keep develop's extracted-section bodies; import-level conflicts resolved to exactly what the merged bodies use (the hook import in, retired-inline-layout imports out).
- **Item 3 (latest-callback collapse):** `profiles.tsx` keeps develop's thin page over `_use-profiles-list-state.ts`; the item-3 mechanics were ported into that hook — `lastEditedProfileRef` became render-read-safe **state**, the bypass re-arm + PUSH bookkeeping moved into an effect on the open transition, and the `openEditDrawerRef` indirection + columns `useMemo` were dropped for a plain handler handed straight to `makeTenantProfileColumns`.
- **Items 4–5:** auto-merges verified intent-preserving (`profiles-new.tsx` hasSavedRef removal; plain handlers + unwrapped columns in `invitations.tsx`/`users.tsx`).
- **Gate + review-r1 comment commits:** `profiles.tsx` conflicts resolved to develop's side — the one carried suppression targeted inline `openEditDrawer` calling `navigate()` directly, a pattern that no longer exists under the hook architecture (`openEditDrawer` now calls `pushSearch`), so there was nothing to suppress; the drawer files' “Pre-existing on develop” rationales merged cleanly.

Full local re-validation on `1b3abb19e`: `pnpm install --frozen-lockfile` (develop had moved the lockfile), front typecheck exit 0, **full front suite green** (design-system guard 0 violations / 718 files, z-index guard OK, React Compiler artifact guard **97 ≥ 72** compiled modules with runtime chunk present — the ported work survives develop's refactor), `just check-write` exit 0, `just react-doctor` zero findings. Full record: `.dump/rebase-report.md`.

Pushed CI on `1b3abb19e`: **all checks green** — 27 pass, spec-drift skipping as designed for a front-only diff (front-e2e 4/4 under the org namespace), zero failures.


## Rebase after #1396, final take (#1384/#1411), tip 6372398 — all green

Develop moved again while take 1 was green in CI: #1384 (pristine-save guard + shared `resolveProfileSaveFailure`) rewrote exactly the tenant profile-edit drawer this PR refactors, plus #1411 (DLQ backend, no overlap). Rebased once more onto that tip; **new tip `6372398b3`** (`--force-with-lease`). Three replay stops conflicted, all in `_profile-edit-details-drawer.tsx`, all resolved keeping both intents: the gate/r1 comment conflicts keep develop's already-justified suppressions with r1's "pre-existing on develop" wording merged in; the round-2 conflict keeps #1384's new save/failure handling while restoring r2's render-phase `seededFor` reseed and event-driven `methods.watch` dirty uplink (zero suppressions introduced — net removal vs pre-#1384 develop).

Local gates on `6372398b3`: typecheck exit 0; full front suite green (React Compiler artifact guard 97 ≥ 72 compiled modules); `just check-write` exit 0; `just react-doctor` zero findings. Lockfile untouched this time, no reinstall needed. Full record: `.dump/rebase-report.md`.

Pushed CI on `6372398b3`: **all checks green** — 27 pass, spec-drift skipping as designed for a front-only diff (front-e2e 4/4 under the org namespace), zero failures; GitHub reports the branch **MERGEABLE**.



---
Implementer: Ox Alpha (Nous Portal, jcode, max effort). Reviews: r1/r2 (free chain), r3 APPROVED at ad6116f, r4 APPROVED by tencent/hy3:free at 6372398 after three rebases onto develop (deltas attributable to #1318/#1385/#1384; 14-commit intent intact; 0 suppressions added); CI 27/27 green incl. front-e2e 4/4.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

anti-slop rungs 4+5 — no-unknown-returns and no-chained-type-assertions at error

1 participant