# Unify Vocabulary + Authority CRUD — 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:** Collapse the duplicated Vocabulary-terms + Authorities CRUD (~280 lines across 4 files) into three shared components, with the rows and pages reduced to thin adapters — behavior-preserving. **Architecture:** Build `LabelledRecordRow`, `LabelledRecordCreateForm`, and `FilteredRecordList` in `src/components/` (Tasks 1-3, additive — existing app untouched, all existing tests stay green). Then rewire `term-row`/`authority-row` and `authorities-page`/`vocabulary-terms` onto them (Task 4) and run the full gate. Variance (mutation hooks, arg shapes, i18n keys, page chrome) lives entirely in the adapters. **Tech Stack:** React 19 + TS + pnpm, TanStack Query v5, react-i18next, Base UI, Vitest 4 (jsdom) + RTL. **Conventions:** pnpm; **no `any`/`eslint-disable`/`@ts-ignore`**; no codename; double-quote+semicolon; token classes only; `components/ui/*` untouched. Run a single test pass per task. **Spec:** `docs/superpowers/specs/2026-06-08-unify-record-crud-design.md` **Key facts:** - Types: `type LabelView = components["schemas"]["LabelView"]`, `type LabelInput = components["schemas"]["LabelInput"]`. `labelText(labels: LabelView[], lang)` (`lib/labels`), `byLabel(lang)` returns a comparator over `{ labels: LabelView[] }` (`lib/sort`). - Shared building blocks (in `src/components/`): `label-editor` (`LabelEditor`, uses `useId` since #62, needs `useConfig` which defaults to `DEFAULTS` — works under `renderApp`), `delete-confirm-dialog` (`DeleteConfirmDialog` — props `description`, `onConfirm: () => Promise`), `mutation-error` (`MutationError` — prop `error: unknown`), `external-uri-link` (`ExternalUriLink` — prop `uri`). - UI kit: `Button`, `Input`, `Label` from `@/components/ui/*`; `ListSkeleton` from `@/components/ui/skeletons` (props `className`, `rows`). - Test harness: `renderApp(ui, { route })` from `../test/render` (wraps QueryClient + memory router + i18n; NO ConfigProvider, but `useConfig` falls back to defaults). `HttpError` is exported from `../api/queries`. - Existing tests that MUST stay green unchanged: `vocab/term-row.test.tsx`, `authorities/authorities.test.tsx`, `vocab/vocabularies.test.tsx`. - Current `term-row.tsx`/`authority-row.tsx` are twins; `authorities-page.tsx` has a kind `