feat(users): introduce discipline (developer / conception / business)#100
Conversation
Every user now carries a mandatory discipline alongside the existing security role, organizational circle, and Anthropic profile. Required at creation, editable, and visible across users table, detail page, profile, and assignment detail. Bulk CSV import accepts an optional discipline column; existing rows preserve their value when the column is blank. Plan, proposal, and implementation notes live in specs/032-user-disciplines/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Introduces a new mandatory discipline attribute on users (developer / conception / business) to support attribution and reporting across non-developer colleagues, without overloading the existing security role.
Changes:
- Adds
user_disciplinePostgres enum +users.disciplineNOT NULL column with defaultdeveloper(and updates seeds/migration metadata). - Extends server actions, validators, and types to require/persist
disciplineon create, allow edits, and preserve values during CSV upserts when the column is blank. - Surfaces
disciplineacross UI (create/edit forms, users table + filter, profile header, assignment detail) and exposes it via/api/profile+ user CSV export.
Reviewed changes
Copilot reviewed 29 out of 32 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/api/profile.test.ts | Updates profile API unit test fixtures to include discipline. |
| tests/unit/agent-auth.test.ts | Updates agent user test fixture to include discipline. |
| src/types/index.ts | Adds UserDiscipline type and threads it through relevant API/input types. |
| src/lib/validators.ts | Adds discipline enum validation for create/update/bulk-import/inline-create schemas. |
| src/lib/utils.ts | Updates CSV diff helper to treat discipline as “only changed when explicitly provided”. |
| src/lib/profile-data.ts | Includes discipline in computed profile payload. |
| src/lib/disciplines.ts | Adds centralized discipline constants, labels, icons, and defensive narrowing helpers. |
| src/lib/db/seed.ts | Seeds admin user with discipline: "developer". |
| src/lib/db/seed-agent-user.ts | Seeds agent user with discipline: "developer". |
| src/lib/db/schema.ts | Adds user_discipline enum and users.discipline column with default. |
| src/lib/db/migrations/meta/0021_snapshot.json | Captures updated Drizzle migration snapshot for the new enum/column. |
| src/lib/db/migrations/meta/_journal.json | Registers migration 0021_wise_vindicator in the migration journal. |
| src/lib/db/migrations/0021_wise_vindicator.sql | Creates enum + adds users.discipline column (default + NOT NULL). |
| src/components/profile/profile-header.tsx | Displays discipline badge (icon + label) on /profile. |
| src/components/inline-user-form.tsx | Requires discipline selection for GitHub sync inline user creation. |
| src/components/edit-user-dialog.tsx | Adds discipline select to the edit-user dialog. |
| src/app/users/users-table.tsx | Adds discipline column (sortable) and faceted filter option for users table. |
| src/app/users/new/new-user-form.tsx | Requires discipline selection when creating a user via /users/new. |
| src/app/users/import/bulk-import-form.tsx | Adds discipline parsing/validation, preview display, and conditional upsert behavior for CSV import. |
| src/app/users/[id]/user-detail-client.tsx | Displays discipline in header and allows editing in the user detail form. |
| src/app/settings/sync/github-member-sync-sheet.tsx | Carries discipline through sync “create user” resolution payload. |
| src/app/assignments/[id]/page.tsx | Passes assignee discipline to client for assignment detail display. |
| src/app/assignments/[id]/assignment-detail-client.tsx | Displays assignee discipline alongside assignment header details. |
| src/app/api/profile/route.ts | Adds discipline to profile API response payload. |
| src/app/api/export/users/route.ts | Adds discipline column to exported users CSV (between circle and role). |
| src/actions/users.ts | Persists discipline on create/update; preserves discipline on CSV upsert when omitted; returns discipline for previews. |
| src/actions/github-sync.ts | Sets discipline on sync-created users (default for import-as-is; explicit from inline-create). |
| specs/032-user-disciplines/proposal.html | Adds spec documentation for the discipline feature proposal. |
| specs/032-user-disciplines/plan.html | Adds implementation plan documentation for discipline rollout. |
| specs/032-user-disciplines/implementation-notes.html | Adds running implementation notes and recorded tradeoffs/deviations. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| role: string; | ||
| /** Discipline value extracted from CSV. Empty string = CSV did not supply a value. */ | ||
| discipline: string; | ||
| /** True when the CSV had a discipline column AND this row populated it. */ |
There was a problem hiding this comment.
Updated the docstring in 776a7ed to match the implementation. Treating column missing and column present but blank the same is intentional for the upsert semantics — both should leave the existing discipline untouched, so the parser doesn't need to distinguish them.
| const newUsers: Array<{ | ||
| githubLogin: string; | ||
| name: string; | ||
| email: string; | ||
| discipline: "developer" | "conception" | "business"; | ||
| }> = []; |
There was a problem hiding this comment.
Fixed in 776a7ed — added UserDiscipline to the existing type import block and used it in the local newUsers array type so the union no longer drifts if the enum expands.
| githubLogin, | ||
| name: defaultName, | ||
| email: defaultEmail, | ||
| discipline: undefined as unknown as InlineUserCreationInput["discipline"], |
There was a problem hiding this comment.
Fixed in 776a7ed — dropped the undefined as unknown as ... cast and omitted the discipline key from defaultValues entirely. zodResolver still enforces it as required at submit time.
| circle: undefined, | ||
| role: "viewer", | ||
| // No default — force a conscious pick. | ||
| discipline: undefined as unknown as UserInput["discipline"], |
There was a problem hiding this comment.
Fixed in 776a7ed — same treatment as the inline form: removed the undefined as unknown as UserInput["discipline"] cast and omitted the key from defaultValues. The Zod resolver still enforces discipline as required at submit.
- bulk-import-form: docstring for `disciplineProvided` now matches the implementation (treats "column missing" and "row blank" the same). - github-member-sync-sheet: use `UserDiscipline` instead of re-declaring the union literal so the type stays in sync with the enum. - inline-user-form / new-user-form: drop `undefined as unknown as ...` casts in RHF `defaultValues`; the DeepPartial shape accepts omission, and zodResolver still enforces a conscious pick on submit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Adds a mandatory discipline field (
developer/conception/business) to every user so the tool can attribute Conception and Business colleagues alongside the existing Developer-only population. Avoids colliding with the existing securityrolecolumn.Spec docs live in
specs/032-user-disciplines/— proposal, plan, and a running implementation-notes log of decisions / deviations / tradeoffs / open questions.user_disciplineenum + NOT NULL column with'developer'default. Migration0021_wise_vindicator.sqlis metadata-only on PG11+ — all 184 existing users backfill todeveloperatomically./profilepage, and assignment detail.disciplinecolumn betweencircleandrole. Bulk import accepts the column as optional; existing rows preserve their value when the column is blank./api/profile) returns the new field. NextAuth session deliberately does not include it.Test plan
pnpm typecheckcleanpnpm lintclean (zero warnings)pnpm test— 339 tests pass/users/newwith discipline=Conception, edited to Business; change-history recordsdiscipline from "conception" to "business"Please select a discipline) when submitting without picking/api/export/usersincludesdisciplinecolumn with backfilled values for all users/profileshows the Developer badge alongside the Admin role badgewt/introduce-roles)Known follow-ups
Captured in
specs/032-user-disciplines/implementation-notes.html:/claude/users,UserListRow,UserDetail) still hide discipline; queue a reporting follow-up to surface discipline as a filter / breakdown.developer(no per-row UI to pick); admin reclassifies in/usersafter the sync. The inline-create path captures discipline explicitly.🤖 Generated with Claude Code