Compare commits

..

2 Commits

Author SHA1 Message Date
rdev-worker
fd7949cb18 build: /design-feature task-management-ui
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-11 02:25:45 +00:00
rdev-worker
9e748a7b97 sdlc: feature
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2026-02-11 02:16:22 +00:00
5 changed files with 694 additions and 7 deletions

View File

@ -2,7 +2,7 @@ slug: data-models
title: Core Data Models & Persistence title: Core Data Models & Persistence
created: 2026-02-11T01:49:19.799933062Z created: 2026-02-11T01:49:19.799933062Z
branch: feature/data-models branch: feature/data-models
phase: planned phase: ready
phase_history: phase_history:
- phase: draft - phase: draft
entered: 2026-02-11T01:49:19.799933062Z entered: 2026-02-11T01:49:19.799933062Z
@ -12,6 +12,9 @@ phase_history:
exited: 2026-02-11T02:16:04.774519802Z exited: 2026-02-11T02:16:04.774519802Z
- phase: planned - phase: planned
entered: 2026-02-11T02:16:04.774519802Z entered: 2026-02-11T02:16:04.774519802Z
exited: 2026-02-11T02:16:20.492312554Z
- phase: ready
entered: 2026-02-11T02:16:20.492312554Z
artifacts: artifacts:
audit: audit:
status: pending status: pending

View File

@ -0,0 +1,614 @@
# Design: Task Management UI
## Architecture Approach
This feature transforms `studio-ui` from a static placeholder dashboard into a functional task management application. It adds client-side routing, API integration, state management, and CRUD pages that consume the REST APIs defined by the `data-models` feature.
| Layer | What Changes |
|-------|-------------|
| **Routing** (`src/App.tsx`) | Replace static content with React Router; add route definitions |
| **Pages** (`src/pages/`) | New page components: ProjectList, ProjectDetail |
| **Components** (`src/components/`) | New feature components: ProjectCard, TaskBoard, TaskColumn, TaskCard, dialogs |
| **Hooks** (`src/hooks/`) | Custom hooks for API data fetching and mutation |
| **API** (`src/api/`) | API client instance and typed request functions |
| **Types** (`src/types/`) | TypeScript interfaces matching backend response shapes |
| **Navigation** (`src/App.tsx`) | Update sidebar items to include Projects route |
The existing shared packages (`ui`, `layout`, `auth`, `api-client`) are consumed as-is. No shared package changes are required.
## Data Model (Frontend Types)
TypeScript interfaces mirroring the backend API response shapes:
```typescript
// src/types/project.ts
interface Project {
id: string;
name: string;
description: string;
status: "active" | "archived";
created_at: string;
updated_at: string;
}
interface CreateProjectRequest {
name: string;
description?: string;
}
interface UpdateProjectRequest {
name: string;
description?: string;
status?: "active" | "archived";
}
// src/types/task.ts
type TaskStatus = "todo" | "in_progress" | "done";
type TaskPriority = "low" | "medium" | "high";
interface Task {
id: string;
project_id: string;
title: string;
description: string;
status: TaskStatus;
priority: TaskPriority;
position: number;
created_at: string;
updated_at: string;
}
interface CreateTaskRequest {
title: string;
description?: string;
priority?: TaskPriority;
}
interface UpdateTaskRequest {
title?: string;
description?: string;
status?: TaskStatus;
priority?: TaskPriority;
position?: number;
}
// src/types/label.ts
interface Label {
id: string;
project_id: string;
name: string;
color: string;
created_at: string;
updated_at: string;
}
interface CreateLabelRequest {
name: string;
color: string;
}
// src/types/assignment.ts
interface Assignment {
id: string;
task_id: string;
assignee: string;
created_at: string;
}
interface CreateAssignmentRequest {
assignee: string;
}
```
All responses from the API are wrapped in the `{data, meta}` envelope:
```typescript
interface APIResponse<T> {
data: T;
meta?: {
request_id: string;
timestamp: string;
};
}
```
## API Integration Layer
### Client Setup
```typescript
// src/api/client.ts
import { createClient } from '@foundary-test-1770773605/api-client';
const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8001';
export const api = createClient({
baseUrl: `${API_BASE}/api/studio-api`,
});
```
### API Functions
Typed wrappers around the API client, one file per resource:
```typescript
// src/api/projects.ts
export async function listProjects(): Promise<Project[]> {
const res = await api.get<APIResponse<Project[]>>('/projects');
return res.data;
}
export async function getProject(id: string): Promise<Project> {
const res = await api.get<APIResponse<Project>>(`/projects/${id}`);
return res.data;
}
export async function createProject(req: CreateProjectRequest): Promise<Project> {
const res = await api.post<APIResponse<Project>>('/projects', req);
return res.data;
}
export async function updateProject(id: string, req: UpdateProjectRequest): Promise<Project> {
const res = await api.put<APIResponse<Project>>(`/projects/${id}`, req);
return res.data;
}
export async function deleteProject(id: string): Promise<void> {
await api.delete(`/projects/${id}`);
}
```
Similar patterns for `src/api/tasks.ts`, `src/api/labels.ts`, `src/api/assignments.ts`. Task and label create/list endpoints use the nested project path (`/projects/{projectId}/tasks`), while get/update/delete use flat paths (`/tasks/{id}`).
## State Management
### Approach: React hooks + local component state
No global state library (Redux, Zustand) is needed. Each page manages its own data via custom hooks that encapsulate fetch/mutate/loading/error state. This matches the simplicity of the current codebase.
### Custom Hooks
```typescript
// src/hooks/useProjects.ts
function useProjects() {
const [projects, setProjects] = useState<Project[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const refresh = useCallback(async () => { ... }, []);
useEffect(() => { refresh(); }, [refresh]);
return { projects, loading, error, refresh };
}
// src/hooks/useProject.ts
function useProject(id: string) {
// Fetches single project + its tasks + labels
const [project, setProject] = useState<Project | null>(null);
const [tasks, setTasks] = useState<Task[]>([]);
const [labels, setLabels] = useState<Label[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const refresh = useCallback(async () => {
// Parallel fetch: project, tasks, labels
const [proj, taskList, labelList] = await Promise.all([
getProject(id),
listTasks(id),
listLabels(id),
]);
setProject(proj);
setTasks(taskList);
setLabels(labelList);
}, [id]);
return { project, tasks, labels, loading, error, refresh };
}
```
After a mutation (create/update/delete), the hook's `refresh()` is called to re-fetch from the server. This avoids stale state and keeps the implementation simple.
## Component Diagram
```
src/
├── App.tsx # Router + DashboardShell layout
├── api/
│ ├── client.ts # API client instance
│ ├── projects.ts # Project API functions
│ ├── tasks.ts # Task API functions
│ ├── labels.ts # Label API functions
│ └── assignments.ts # Assignment API functions
├── types/
│ ├── project.ts # Project types
│ ├── task.ts # Task types
│ ├── label.ts # Label types
│ └── assignment.ts # Assignment types
├── hooks/
│ ├── useProjects.ts # Project list data
│ └── useProject.ts # Single project + tasks + labels
├── pages/
│ ├── ProjectListPage.tsx # /projects route
│ └── ProjectDetailPage.tsx # /projects/:id route
├── components/
│ ├── ProjectCard.tsx # Card for project list
│ ├── CreateProjectDialog.tsx # Dialog for creating/editing projects
│ ├── DeleteConfirmDialog.tsx # Reusable delete confirmation
│ ├── TaskBoard.tsx # Kanban-style board with columns
│ ├── TaskColumn.tsx # Single status column
│ ├── TaskCard.tsx # Individual task card
│ ├── CreateTaskDialog.tsx # Dialog for creating/editing tasks
│ ├── LabelList.tsx # Label management section
│ ├── CreateLabelDialog.tsx # Dialog for creating labels
│ └── AssignmentList.tsx # Assignment display in task detail
├── index.css
├── main.tsx
└── lib/
└── logger.ts
```
### Component Interaction Flow
```
App.tsx
├── DashboardShell (from @layout)
│ ├── Sidebar → nav items including /projects
│ ├── Header
│ └── <Routes>
│ ├── /projects → ProjectListPage
│ │ ├── useProjects() hook
│ │ ├── ProjectCard[] (grid)
│ │ └── CreateProjectDialog
│ │
│ └── /projects/:id → ProjectDetailPage
│ ├── useProject(id) hook
│ ├── Project header (name, status, actions)
│ ├── TaskBoard
│ │ ├── TaskColumn (todo)
│ │ │ └── TaskCard[]
│ │ ├── TaskColumn (in_progress)
│ │ │ └── TaskCard[]
│ │ └── TaskColumn (done)
│ │ └── TaskCard[]
│ ├── CreateTaskDialog
│ ├── LabelList
│ │ └── CreateLabelDialog
│ └── DeleteConfirmDialog
```
## Page Designs
### Project List Page (`/projects`)
```
┌─────────────────────────────────────────────────┐
│ Header: "Projects" [+ New Project] │
├─────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Project A │ │ Project B │ │ Project C │ │
│ │ │ │ │ │ │ │
│ │ Desc... │ │ Desc... │ │ Desc... │ │
│ │ │ │ │ │ │ │
│ │ ●Active │ │ ●Active │ │ ○Archived │ │
│ │ 5 tasks │ │ 12 tasks │ │ 3 tasks │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ No projects yet? Create your first one! │ │
│ │ [+ Create Project] │ │
│ └──────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────┘
```
- Grid layout: `grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4`
- Empty state card when no projects exist
- Project cards link to `/projects/:id`
- Badge shows status (success for active, secondary for archived)
### Project Detail Page (`/projects/:id`)
```
┌─────────────────────────────────────────────────┐
│ Projects > Project A [Edit] [Del] │
├─────────────────────────────────────────────────┤
│ │
│ Labels: [Bug] [Feature] [Docs] [+ Add Label] │
│ │
│ ┌─────────────┬──────────────┬───────────────┐ │
│ │ Todo (3) │ In Progress │ Done (5) │ │
│ │ │ (2) │ │ │
│ │ ┌─────────┐│ ┌──────────┐ │ ┌───────────┐ │ │
│ │ │ Task 1 ││ │ Task 4 │ │ │ Task 6 │ │ │
│ │ │ ▲ High ││ │ ● Medium │ │ │ ▼ Low │ │ │
│ │ │ @alice ││ │ @bob │ │ │ @alice │ │ │
│ │ └─────────┘│ └──────────┘ │ └───────────┘ │ │
│ │ ┌─────────┐│ │ ┌───────────┐ │ │
│ │ │ Task 2 ││ │ │ Task 7 │ │ │
│ │ │ ● Med ││ │ │ ● Medium │ │ │
│ │ └─────────┘│ │ └───────────┘ │ │
│ │ │ │ │ │
│ │ [+ Add] │ │ │ │
│ └─────────────┴──────────────┴───────────────┘ │
│ │
└─────────────────────────────────────────────────┘
```
- Kanban board with three fixed columns: Todo, In Progress, Done
- Each column has a header with count badge
- Task cards show title, priority badge, assignee(s)
- Clicking a task card opens an edit sheet/dialog
- "Add Task" button at the bottom of the Todo column (or a floating button)
- Labels displayed as a horizontal row of badges above the board
## Key Component Specifications
### ProjectCard
```tsx
// Uses: Card, CardHeader, CardTitle, CardDescription, CardContent, Badge
// Props: project: Project, taskCount?: number, onClick: () => void
// Behavior: Click navigates to /projects/:id
```
### TaskBoard
```tsx
// Props: tasks: Task[], onCreateTask, onUpdateTask, onDeleteTask
// Groups tasks by status into three TaskColumn components
// Horizontal scroll on narrow viewports: flex with overflow-x-auto
```
### TaskCard
```tsx
// Uses: Card, Badge
// Props: task: Task, assignments: Assignment[], onClick: () => void
// Shows: title, priority badge (color-coded), assignee avatars/names
// Click opens task edit dialog
```
### Priority Badge Colors
| Priority | Badge Variant | Color |
|----------|--------------|-------|
| High | `error` | Red |
| Medium | `warning` | Yellow/Amber |
| Low | `info` | Blue |
### Status Column Colors
| Status | Label |
|--------|-------|
| `todo` | "Todo" |
| `in_progress` | "In Progress" |
| `done` | "Done" |
### CreateProjectDialog
```tsx
// Uses: Dialog, Input, Textarea, Button, Label
// Fields: name (required, max 200), description (optional, max 1000)
// Mode: create (POST) or edit (PUT) based on whether project prop is passed
// On submit: calls API, then refresh() from hook
```
### CreateTaskDialog
```tsx
// Uses: Dialog, Input, Textarea, Select, Button, Label
// Fields: title (required, max 300), description (optional, max 5000),
// priority (select: low/medium/high), status (select: todo/in_progress/done)
// Mode: create (POST, status defaults to todo) or edit (PUT)
```
### DeleteConfirmDialog
```tsx
// Uses: Dialog, Button
// Props: title, message, onConfirm, onCancel
// "Are you sure?" with destructive-variant confirm button
// Reusable for projects, tasks, labels
```
## Routing Setup
```tsx
// src/App.tsx
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<DashboardShell sidebar={<Sidebar items={navItems} />} header={...}>
<Routes>
<Route path="/" element={<Navigate to="/projects" replace />} />
<Route path="/projects" element={<ProjectListPage />} />
<Route path="/projects/:id" element={<ProjectDetailPage />} />
</Routes>
</DashboardShell>
</BrowserRouter>
);
}
```
The sidebar `navItems` will be updated:
```tsx
const navItems: NavItem[] = [
{ label: 'Projects', href: '/projects', icon: FolderKanban, active: true },
{ label: 'Settings', href: '/settings', icon: Settings },
];
```
Active state should be derived from current route via `useLocation()`.
## Error Handling Strategy
### API Error Handling
All API functions handle the `{data, meta}` envelope and throw on non-2xx responses:
```typescript
// Errors propagated from api-client
try {
await createProject(req);
} catch (err) {
// err.status: HTTP status code
// err.message: error message from server
// err.body: full error response
}
```
### Per-Component Error States
| Scenario | UI Behavior |
|----------|-------------|
| Page load failure | Alert banner: "Failed to load projects. Try again." + retry button |
| Create/Update validation (400/422) | Field-level error messages in dialog |
| Duplicate name conflict (409) | Inline error: "A project with this name already exists" |
| Not found (404) | Redirect to project list with toast/alert |
| Network error | Alert banner: "Network error. Check your connection." |
| Delete failure | Toast/alert: "Failed to delete. Try again." |
### Error Display Components
Errors are shown using the existing `Alert` component:
```tsx
<Alert variant="destructive">
<AlertTitle>Error</AlertTitle>
<AlertDescription>{error.message}</AlertDescription>
</Alert>
```
## Security Considerations
### Input Validation (Client-Side)
Client-side validation mirrors backend constraints to provide immediate feedback:
- Project name: required, 1-200 chars
- Project description: max 1000 chars
- Task title: required, 1-300 chars
- Task description: max 5000 chars
- Label name: required, 1-50 chars
- Label color: hex format validation (#RRGGBB)
- Assignee: required, 1-200 chars
Client-side validation is **defense in depth** — the backend always re-validates.
### XSS Prevention
- React's JSX auto-escapes rendered content — no `dangerouslySetInnerHTML` used
- User-provided strings (names, descriptions) rendered as text nodes
- Label colors rendered only as CSS `background-color` values, validated as hex
### Authentication
- Auth is out of scope per spec — no login UI or token management in this feature
- The API client supports bearer token injection when auth is later enabled
- Write operations (create/update/delete) work without auth in development (matching backend's `AUTH_ENABLED=false` default)
### CORS
- Dev mode: Vite proxy or CORS headers configured for `localhost:3001``localhost:8001`
- Production: Same-origin or configured CORS in the backend
## Performance Considerations
### Data Fetching
- **Project list:** Single API call (`GET /projects`). No pagination needed for initial scale.
- **Project detail:** Three parallel API calls (`GET /projects/:id`, `GET /projects/:id/tasks`, `GET /projects/:id/labels`). Uses `Promise.all` for concurrent loading.
- **Assignments:** Fetched lazily when a task card is expanded/clicked, not on page load. This avoids N+1 API calls.
### Rendering
- Task board: Three columns rendered as simple lists. No virtualization needed at current scale (< 100 tasks per project).
- React key optimization: All lists keyed by entity `id` (UUID).
- Dialog components: Rendered conditionally (not mounted until triggered).
### Bundle Size
- No additional large dependencies. Uses existing React, React Router, and shared packages.
- lucide-react icons are tree-shaken per import.
- No chart libraries or heavy visualization for this feature.
### Caching
- No client-side cache layer in v1. Each page navigation re-fetches.
- Future optimization: add SWR or React Query for caching, deduplication, and background refetching.
## Migration / Rollout Plan
### Prerequisites
1. **Backend:** The `data-models` feature must be implemented and deployed first. The API endpoints (`/projects`, `/tasks`, etc.) must be available.
2. **Database:** PostgreSQL with migrations applied (handled by `data-models` feature).
### Rollout Steps
**Step 1: Add routing and page structure**
- Install `react-router-dom` (if not already present)
- Replace static `App.tsx` content with router setup
- Create empty page components
**Step 2: Add API integration layer**
- Create `src/api/client.ts` with base configuration
- Create typed API function files per resource
- Create TypeScript type definitions
**Step 3: Build project list page**
- `ProjectListPage` with `useProjects` hook
- `ProjectCard` component
- `CreateProjectDialog` (create + edit modes)
- `DeleteConfirmDialog` (reusable)
**Step 4: Build project detail page**
- `ProjectDetailPage` with `useProject` hook
- `TaskBoard` + `TaskColumn` + `TaskCard`
- `CreateTaskDialog` (create + edit modes)
- `LabelList` + `CreateLabelDialog`
- `AssignmentList` within task edit
**Step 5: Polish and integration**
- Loading states, error states, empty states
- Navigation active states from route
- Environment variable for API URL
### Backward Compatibility
- The static dashboard content is **replaced**, not preserved alongside
- No existing user data to migrate — this is a new feature
- The sidebar navigation changes (removes placeholder items, adds Projects)
- No API contract changes — this is a pure frontend addition
### Environment Variables
| Variable | Default | Purpose |
|----------|---------|---------|
| `VITE_API_URL` | `http://localhost:8001` | Backend API base URL |
### Testing Strategy
- **Manual testing:** All CRUD operations through the UI against the running backend
- **Component tests:** Optional unit tests for complex components (TaskBoard grouping logic)
- **E2E tests:** Out of scope for v1; can be added later with Playwright/Cypress
## Design Decisions
1. **React hooks over global state:** The app is simple enough that local state per page is sufficient. No Redux/Zustand complexity. Custom hooks encapsulate fetch logic and make it reusable.
2. **Kanban board over table view:** Task management benefits from visual status columns. The three-column board (Todo, In Progress, Done) provides immediate status overview. Table view can be added as an alternative later.
3. **Dialogs over separate pages for CRUD:** Create/edit operations use modal dialogs rather than navigating to separate form pages. This keeps the user in context and matches the design system's Dialog component.
4. **Lazy assignment loading:** Assignments are fetched per-task when clicked, not upfront. This avoids N+1 queries on page load and keeps initial render fast.
5. **No drag-and-drop in v1:** The backend has a `position` field, but implementing drag-and-drop adds significant complexity (library dependency, position recalculation, optimistic updates). Deferred to a follow-up feature.
6. **Shared DeleteConfirmDialog:** One reusable confirmation dialog rather than per-entity delete dialogs. Reduces code duplication.
7. **Environment-based API URL:** Uses `VITE_API_URL` for the backend base URL. In production, this can point to the deployed API. In development, it defaults to `localhost:8001`.
8. **No client-side routing library beyond React Router:** The sidebar's `NavItem` pattern already supports `href` strings. Active state detection uses `useLocation()` from React Router.

View File

@ -13,7 +13,7 @@ artifacts:
status: pending status: pending
path: audit.md path: audit.md
design: design:
status: pending status: draft
path: design.md path: design.md
qa_plan: qa_plan:
status: pending status: pending

View File

@ -0,0 +1,69 @@
# Feature: Task Management UI
## Problem Statement
The studio application has a static placeholder dashboard with no real functionality. The `data-models` feature provides backend REST APIs for Projects, Tasks, Labels, and Assignments, but there is no frontend to interact with them. Users need a task management interface in `studio-ui` to create and manage projects, organize tasks with statuses and priorities, and assign work — all through an intuitive board/list UI built on the shared design system.
## User Stories
- As a **user**, I want to see a list of my projects so I can navigate between them.
- As a **user**, I want to create, edit, and delete projects so I can organize my work.
- As a **user**, I want to see tasks within a project organized by status (todo, in_progress, done) so I can track progress.
- As a **user**, I want to create, edit, and delete tasks within a project so I can manage work items.
- As a **user**, I want to set task priority (low, medium, high) so I can focus on what matters.
- As a **user**, I want to assign and unassign people to tasks so I can distribute work.
- As a **user**, I want to create and manage labels within a project so I can categorize work.
- As a **user**, I want the UI to use the shared design system (dark theme, CSS variables) so it looks consistent.
## Acceptance Criteria
### Navigation & Routing
- [ ] React Router routes: `/projects`, `/projects/:id`, `/projects/:id/settings`
- [ ] Sidebar navigation includes "Projects" item with icon
- [ ] Breadcrumb navigation within project views
### Project List Page (`/projects`)
- [ ] Displays all projects as cards in a grid layout
- [ ] Each card shows name, description, status badge, task count
- [ ] "New Project" button opens a create dialog
- [ ] Project cards link to project detail view
### Project Detail Page (`/projects/:id`)
- [ ] Header with project name, status, and action buttons (edit, delete)
- [ ] Task board view with columns: Todo, In Progress, Done
- [ ] Each task card shows title, priority badge, assignees
- [ ] "Add Task" button opens create task dialog
- [ ] Labels section showing project labels
### CRUD Dialogs
- [ ] Create/Edit Project dialog: name (required), description (optional)
- [ ] Create/Edit Task dialog: title (required), description, priority selector, status selector
- [ ] Create Label dialog: name (required), color picker
- [ ] Delete confirmation dialogs for destructive actions
### API Integration
- [ ] Uses `@foundary-test-1770773605/api-client` for all API calls
- [ ] Proper loading states during API calls
- [ ] Error handling with user-friendly messages
- [ ] Optimistic UI updates where appropriate
### Responsive Design
- [ ] Works on desktop (1024px+) and tablet (768px+)
- [ ] Task board scrolls horizontally on smaller screens
## Dependencies
- `data-models` feature (backend APIs for Projects, Tasks, Labels, Assignments)
- `@foundary-test-1770773605/ui` (shared component library)
- `@foundary-test-1770773605/layout` (dashboard shell)
- `@foundary-test-1770773605/api-client` (typed HTTP client)
- `react-router-dom` (client-side routing)
## Out of Scope
- Drag-and-drop task reordering (future feature)
- Real-time updates via WebSockets
- Pagination or infinite scroll
- Advanced filtering/search within tasks
- Task-label association UI (backend not implemented yet)
- Authentication/login UI

View File

@ -4,12 +4,13 @@ project:
active_work: active_work:
features: features:
- slug: data-models - slug: data-models
phase: planned branch: feature/data-models
phase: ready
- slug: task-management-ui - slug: task-management-ui
phase: specified phase: specified
blocked: [] blocked: []
last_updated: 2026-02-11T02:16:10.442089978Z last_updated: 2026-02-11T02:16:20.493007011Z
last_action: CREATE_BRANCH last_action: TRANSITION
last_actor: cli last_actor: cli
history: history:
- timestamp: 2026-02-11T01:49:19.80029367Z - timestamp: 2026-02-11T01:49:19.80029367Z
@ -62,8 +63,8 @@ history:
feature: data-models feature: data-models
actor: cli actor: cli
result: success result: success
- timestamp: 2026-02-11T02:16:10.442088626Z - timestamp: 2026-02-11T02:16:20.493005579Z
action: CREATE_BRANCH action: TRANSITION
feature: data-models feature: data-models
actor: cli actor: cli
result: success result: success