feat: Add custom step creation and backend support (Phase 3A: B.8-B.10, B.13)

Implements custom step creation forms and backend persistence:

Task B.8 - StepForm Component:
- Comprehensive form for creating custom steps
- Step type selection (decision/action/solution) with descriptions
- Required fields: title, instructions (markdown supported)
- Optional fields: help text, commands (dynamic array), category, tags
- Visibility control (private/team/public)
- Save to library checkbox
- Full validation with error display
- Dynamic command management (add/remove, label + command)
- Tag input with Enter key support

Task B.9 - CustomStepModal:
- Tabbed modal interface
- Tab 1: "Type My Own" - embeds StepForm
- Tab 2: "Browse Library" - embeds StepLibraryBrowser
- Handles both saved steps (API) and drafts (no save)
- Loading states during step creation
- Error handling with user feedback
- Returns Step or CustomStepDraft to parent

Task B.10 - Backend Custom Steps Support:
- Database migration: add custom_steps JSONB column to sessions
- Updated Session model with custom_steps field
- Updated SessionResponse schema with custom_steps
- Updated SessionUpdate schema to accept custom_steps
- Migration ready to run: 4cdb5cba1aff

Task B.13 - Session Types Updates:
- Added CustomStep and CustomStepDraft interfaces
- Updated Session interface with custom_steps field
- Updated SessionUpdate interface
- Exported step types from types/index.ts
- Full TypeScript support for custom step integration

Remaining tasks: B.11 (TreeNavigationPage integration), B.12 (Export)
Build tested successfully.

Related: Issues #8, #9, #10

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-02-03 19:15:36 -05:00
parent fc7fa1a17c
commit 009c60fbc3
7 changed files with 602 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
"""add_custom_steps_to_sessions
Revision ID: 4cdb5cba1aff
Revises: 008
Create Date: 2026-02-03 19:12:42.551966
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import JSONB
# revision identifiers, used by Alembic.
revision: str = '4cdb5cba1aff'
down_revision: Union[str, None] = '008'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# Add custom_steps JSONB column to sessions table
op.add_column('sessions',
sa.Column('custom_steps', JSONB, nullable=False, server_default='[]')
)
def downgrade() -> None:
# Remove custom_steps column from sessions table
op.drop_column('sessions', 'custom_steps')

View File

@@ -30,6 +30,7 @@ class Session(Base):
tree_snapshot: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False)
path_taken: Mapped[list[str]] = mapped_column(JSONB, nullable=False, default=list)
decisions: Mapped[list[dict[str, Any]]] = mapped_column(JSONB, nullable=False, default=list)
custom_steps: Mapped[list[dict[str, Any]]] = mapped_column(JSONB, nullable=False, default=list)
started_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),

View File

@@ -24,6 +24,7 @@ class SessionCreate(BaseModel):
class SessionUpdate(BaseModel):
path_taken: Optional[list[str]] = None
decisions: Optional[list[DecisionRecord]] = None
custom_steps: Optional[list[dict[str, Any]]] = None
ticket_number: Optional[str] = Field(None, max_length=100)
client_name: Optional[str] = Field(None, max_length=255)
@@ -35,6 +36,7 @@ class SessionResponse(BaseModel):
tree_snapshot: dict[str, Any]
path_taken: list[str]
decisions: list[dict[str, Any]]
custom_steps: list[dict[str, Any]] = Field(default_factory=list)
started_at: datetime
completed_at: Optional[datetime] = None
ticket_number: Optional[str] = None