Estimate: 18m
The Story's entry-point write surface. A thin Server Action
createIssue at app/(authed)/projects/[key]/_actions/createIssue.ts
calls the shipped workItemsService.createWorkItem (Story 1.4) — which already
owns key allocation (via projectRepository.allocateWorkItemNumber), initial-status
seeding (per 2.2.4, reads the project's workflow_status with isInitial=true),
parentage gate (via 2.1.2's assertValidParent), and the create-revision audit row.
This Subtask does not extend the service — it builds the Server Action wrapper
Modal UI. Reusable CreateIssueModal client component, opened
three ways: (1) the existing top-nav "+" button from Story 1.5.3's shell slot, (2) global "C" keyboard shortcut wired into lib/shortcuts.ts (the cheatsheet from 1.5.4 picks
it up automatically), (3) ⌘K palette command "Create issue" (1.5.4's
AppCommandPalette). Modal is a Radix Dialog with form fields in this order:
Type (defaults to task; the picker is the filtered combobox
from 2.3.4), Parent (optional; same combobox, filtered by 2.1.2's
canParent(parentKind, selectedType)), Title (required, single
line, max 200 chars per existing Story 1.4 validation), Description
(optional, the MarkdownEditor primitive from 2.3.5, compact "min" mode = textarea
medium per the schema). Reporter is NEVER a form
field — always the current session user (the schema requires non-null reporter; defaulting
server-side keeps the form lean and matches Jira/Linear's standard shape).Submit flow. Optimistic UX is OFF for create (the new key is server-generated
via the project counter — there's no value to optimistic-render). On submit: form validation
(zod schema matching the service's expected DTO) → call createIssue Server Action
→ service throws either IllegalParentTypeError (422),
ProjectNotFoundError (404, cross-workspace defense), or
WorkspaceContextMissingError (500, infra bug); these map to inline form errors
(parent field) or a toast (project/workspace). On success the modal closes, a toast surfaces
the new identifier (e.g. "PROD-42 created" with the identifier as a link to the future detail
page — for now it links to /projects/[key]/issues/[key]/edit from 2.3.6), and
revalidatePath refreshes the active list view.
What this Subtask does NOT do: rich-text image upload (2.3.7), full
edit form (2.3.6), the filtered type+parent combobox internals (2.3.4 — this Subtask
imports it), the MarkdownEditor primitive internals (2.3.5 — imports it). If
2.3.4 or 2.3.5 haven't landed by the time this runs, the modal uses temporary stubs (a
plain <select> for type + a basic textarea for description) and swaps
to the real components when those land — a comment marks each stub.
createIssue at the documented path; pure 4-layer thin wrapper (parse input → resolve workspaceId from session → call workItemsService.createWorkItem → return DTO or typed error). No business logic.task), Title (max 200 chars). Title-empty + Title-overflow surface inline form errors before submit./projects/[key]/issues/[key]/edit; list view revalidates; modal closes.IllegalParentTypeError from the service surfaces as an inline error on the Parent field (not a toast); cross-workspace project rejection 404s the action.reporterId attribute is IGNORED (the Server Action doesn't read it).tests/issues/createIssueAction.test.ts: the action calls the service with the expected DTO, propagates typed errors, ignores client-supplied reporter, returns the created DTO. Service tests are NOT re-run here — 1.4.x already covers createWorkItem.tests/components/CreateIssueModal.test.tsx: the three entry points open it; required-field validation; submit calls the action with the form's values.shell-a11y.spec.ts) extended to render the modal in its default-open state — zero axe violations.lib/services/workItemsService.ts (Story 1.4) — createWorkItem signature + the typed errors it throwslib/issues/parentRules.ts (2.1.2) — canParent, consumed by 2.3.4's picker but referenced here for the error pathlib/shortcuts.ts (1.5.4) — register "C" alongside the existing shortcuts; the cheatsheet auto-picks it upcomponents/ui/CommandPalette.tsx + AppCommandPalette (1.5.4) — register the "Create issue" commandmotir-core/CLAUDE.md — Server Action / 4-layer rules