> ## Documentation Index
> Fetch the complete documentation index at: https://exa.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 2026 08 24 design button behavioral stability

# Design Button Behavioral Stability Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Make native Button intent explicit, test its behavioral contract, and block design-system changes from merging when they break any direct monorepo consumer.

**Architecture:** Keep the runtime-safe `type="button"` fallback in the shared component while making direct JSX usage mandatory through a focused ESLint rule. The Button type is lint-enforced, with a defensive runtime default. Audit the dependency graph from package metadata, migrate each direct Button use according to its form/event context, add DOM-level component and dashboard regression tests, and extend the existing design-system workflow with metadata-derived downstream checks that use each package's canonical build surface.

**Tech Stack:** React 19, TypeScript, ESLint 9 RuleTester, Vitest/jsdom, pnpm, Nix/exapkgs, GitHub Actions.

***

### Task 1: Freeze the incident as failing contracts

**Files:**

* Modify: `node/apps/design/package.json`

* Modify: `node/apps/design/pnpm-lock.yaml`

* Create: `node/apps/design/src/ui/button.test.tsx`

* Create: `node/apps/dashboard/src/app/(app)/(dashboard)/team-settings/AddableTableDialogForm.tsx`

* Create: `node/apps/dashboard/src/app/(app)/(dashboard)/team-settings/AddableTableDialogForm.vitest.tsx`

* Modify: `node/apps/dashboard/src/app/(app)/(dashboard)/team-settings/TeamSettingsPageClient.tsx`

* Modify: `node/apps/dashboard/package.json`

* Modify: `node/pnpm-lock.yaml`

* [ ] Add the existing repository versions of Vitest/jsdom and React DOM test helpers to the design package, plus a `test:button`/unit-test script that runs DOM tests without weakening the existing Node test suite.

* [ ] Write Button behavioral tests that assert the rendered DOM `type`, submit activation by click and keyboard, non-submission for `type="button"`, and no click/submit action while disabled or loading.

* [ ] Extract only the shared team-settings dialog form markup into `AddableTableDialogForm` so it can be exercised without mocking the entire 1,600-line page; keep styling, request handlers, and the merged `type="submit"` hotfix unchanged.

* [ ] Write dashboard DOM tests that fill and click the visible submit control for both "Send invite" and "Add IP" configurations and assert the supplied submit handler fires; add pending/disabled coverage.

* [ ] Run the new focused tests once against the pre-contract code and confirm they encode actual interaction rather than source-string presence.

### Task 2: Require Button intent in TypeScript and ESLint

**Files:**

* Modify: `node/apps/design/src/ui/button.tsx`

* Modify: `node/apps/design/src/eslint/rules.js`

* Modify: `node/apps/design/src/eslint/rules.test.js`

* Modify: `node/apps/design/src/eslint/index.js`

* Modify: `node/apps/design/src/docs/consumer-lint-rules.json`

* Modify: `node/apps/design/docs/eslint.md`

* [ ] Keep `ButtonProps.type?: "button" | "submit" | "reset"` and retain `type = "button"` inside the runtime implementation for JavaScript/untyped callers; enforce explicit source intent with the focused ESLint rule.

* [ ] Add `@exa/design/require-button-type`, tracking named imports and aliases from `@exa/design/ui/button` and reporting direct JSX uses with no explicit `type` attribute.

* [ ] Document the deterministic policy: a spread or dynamic `type` does not satisfy the direct-JSX lint rule because intent must be visible at the use site; wrappers/re-exports must preserve native type intent at their forwarding boundaries.

* [ ] Add RuleTester cases for all three valid literal types, aliases, unrelated local `Button` components, missing type, spread-only props, and explicit dynamic type.

* [ ] Enable the rule in the shared preset and keep the docs/rule catalog drift test green.

* [ ] Run `node --test src/eslint/rules.test.js src/eslint/consumer-rules-doc.test.js` and the focused Button test.

### Task 3: Audit and migrate every Button callsite

**Files:**

* Modify: direct Button consumers under every package discovered from `package.json` metadata, including `go/cortex/ui`, `infra/*`, `node/apps/*`, and `typescript/*`

* Modify: Button examples and internal consumers under `node/apps/design/src` as required by the new type contract

* Create: `.context/design-button-stability/button-audit.tsv` (gitignored audit evidence)

* [ ] Generate an audit table from the TypeScript AST that records package, file, imported local name, wrapper/re-export status, enclosing form (where statically visible), handler, disabled/loading state, and chosen `button|submit|reset` intent.

* [ ] Review form ancestry and handler behavior before editing: use `submit` only for controls intended to invoke a form's `onSubmit`, `reset` only for actual resets, and `button` for triggers/actions (especially dialogs/popovers nested in forms).

* [ ] Apply the reviewed mechanical edits, including aliased imports, wrappers, styled wrappers, docs examples, and tests; do not change visual props or product behavior.

* [ ] Re-run an AST inventory and TypeScript checks to prove no typed Button or wrapper callsite remains ambiguous.

* [ ] Record the total changed callsites and the reviewed submit/reset list for the final handoff.

### Task 4: Make semantic safety lint app-wide

**Files:**

* Modify: `node/apps/dashboard/eslint.config.mjs`

* Modify: direct-consumer ESLint configs where they scope the visual preset or compose only selected rules

* Create: `node/apps/design/scripts/check-consumer-button-safety.mjs`

* Test: `node/apps/design/scripts/check-consumer-button-safety.test.mjs`

* [ ] Export a semantic-safety config (or rule block) containing `require-button-type` independently from visual migration rules, without relaxing the existing recommended preset.

* [ ] Update dashboard so `require-button-type` applies to all application TS/JS while `MIGRATED_PATHS` continues to scope visual/component compliance.

* [ ] Apply the same app-wide safety rule to every direct consumer with ESLint; where a consumer uses oxlint or has no ESLint, rely on the TypeScript gate and record that limitation explicitly.

* [ ] Add a metadata-derived checker that discovers every direct `@exa/design` package and verifies its source is covered either by the ESLint safety rule or by a TypeScript compatibility check.

* [ ] Test discovery against fixtures plus the live monorepo graph so newly added consumers cannot silently skip the gate.

### Task 5: Add downstream compatibility CI

**Files:**

* Modify: `.github/workflows/design-system.yaml`

* Create: `node/apps/design/scripts/list-consumers.mjs`

* Create: `node/apps/design/scripts/list-consumers.test.mjs`

* Modify: `node/apps/design/package.json`

* Modify: `node/apps/design/project.nix` only if a reusable Nix passthru is needed

* Modify: relevant canonical consumer `project.nix` files only when their existing output does not expose the package's build/typecheck/test target

* [ ] Implement consumer discovery by scanning tracked application `package.json` files for dependencies/devDependencies/peerDependencies on `@exa/design`; emit stable JSON with package name, directory, nearest canonical Nix project/flake, and available `typecheck`, `build`, `lint`, and behavioral-test scripts.

* [ ] Add unit tests for workspace, `file:`, nested-package, missing-script, and newly discovered consumer cases.

* [ ] Extend the design-system PR workflow with a discovery job and a blocking matrix. Always run design lint/typecheck/unit tests plus every consumer typecheck and relevant contract test; run production builds through each consumer's canonical Nix/build definition where available.

* [ ] Avoid a handwritten package list: the matrix must come from the discovery script, and an unclassifiable consumer must fail discovery with an actionable error rather than being skipped.

* [ ] Add a scheduled full consumer matrix only if the blocking PR build set must be reduced for measured cost; preserve at least one blocking downstream check capable of detecting compile/API incompatibility.

* [ ] Validate workflow syntax and dry-run/inspect the emitted matrix locally.

### Task 6: Document behavioral compatibility as public API

**Files:**

* Modify: `node/apps/design/AGENTS.md`

* Modify: `node/apps/design/public/AGENTS.md`

* Modify: `node/apps/design/public/CHECKLIST.md`

* Modify: `node/apps/design/DECISIONS.md`

* Modify: `node/apps/design/docs/eslint.md`

* Modify: `node/apps/dashboard/AGENTS.md`

* [ ] Add a new durable decision stating that DOM element choice, form semantics, callbacks/payloads, controlled state, focus, dismissal, keyboard interaction, and disabled/loading behavior are public API and require downstream interaction tests.

* [ ] Require an explicit Button type in contributor and consumer guidance; state why the runtime default remains `button` and why visual parity is insufficient.

* [ ] Add the migration/review checklist covering rendered HTML, forms, link-vs-action, keyboard/focus, controlled state, callbacks, disabled/loading, popup dismissal, analytics/requests, and behavioral regression tests.

* [ ] State that safety-oriented lint is app-wide and independent of gradual visual migration coverage.

* [ ] Update dashboard's shared-component incident guidance with this form-submission regression and the new prevention gates.

### Task 7: Format and validate the complete change

**Files:**

* Modify: only formatter output in files already changed

* [ ] Run `format --branch origin/master` per the monorepo code-quality contract and inspect the resulting diff for unrelated rewrites.

* [ ] From `node/apps/design`, run lint, typecheck, unit tests, the ESLint RuleTester suite, the Button DOM tests, and production build.

* [ ] From `node/apps/dashboard`, run format/check, lint (including app-wide semantic safety), typecheck, the shared team-settings regression tests, relevant unit tests, and production build with documented fake environment variables.

* [ ] Execute the compatibility checker/matrix locally for all metadata-derived direct consumers, using canonical Nix outputs where available; capture exact command/failure/reason for any environment-blocked package.

* [ ] Run `git diff --check`, recount changed Button callsites, and compare the final consumer inventory with the initial package/import scans.

* [ ] Review `git diff origin/master...` for UI redesign, accidental runtime-default changes, broad lint suppressions, stale generated artifacts, or changes outside the requested failure class.
